Jak wspomniano w wielu moich poprzednich pytaniach, pracuję przez K&R i obecnie używam preprocesora. Jedną z bardziej interesujących rzeczy - czymś, czego nigdy wcześniej nie wiedziałem z żadnej z moich poprzednich prób nauki C - jest ##
operator preprocesora. Według K&R:
Operator preprocesora
##
umożliwia konkatenację rzeczywistych argumentów podczas rozwijania makr. Jeśli parametr w zastępczym tekście sąsiaduje z a##
, parametr jest zastępowany przez rzeczywisty argument,##
biały znak i otaczający go odstęp są usuwane, a wynik jest ponownie skanowany. Na przykład makropaste
łączy dwa argumenty:
#define paste(front, back) front ## back
więc
paste(name, 1)
tworzy tokenname1
.
Jak i dlaczego ktoś miałby to wykorzystać w prawdziwym świecie? Jakie są praktyczne przykłady jego użycia i czy należy wziąć pod uwagę pewne kwestie?
std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__);
i niejawnie skompilować cały ciąg naraz.Jedna rzecz, o której należy pamiętać, gdy używasz token-paste („
##
”) lub stringizing („#
operatorów przetwarzania wstępnego '), jest to, że musisz użyć dodatkowego poziomu pośredniego, aby działały poprawnie we wszystkich przypadkach.Jeśli tego nie zrobisz, a elementy przekazane do operatora wklejania tokenów są same w sobie makrami, otrzymasz wyniki, które prawdopodobnie nie są tym, czego oczekujesz:
#include <stdio.h> #define STRINGIFY2( x) #x #define STRINGIFY(x) STRINGIFY2(x) #define PASTE2( a, b) a##b #define PASTE( a, b) PASTE2( a, b) #define BAD_PASTE(x,y) x##y #define BAD_STRINGIFY(x) #x #define SOME_MACRO function_name int main() { printf( "buggy results:\n"); printf( "%s\n", STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__))); printf( "%s\n", BAD_STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__))); printf( "%s\n", BAD_STRINGIFY( PASTE( SOME_MACRO, __LINE__))); printf( "\n" "desired result:\n"); printf( "%s\n", STRINGIFY( PASTE( SOME_MACRO, __LINE__))); }
Wyjście:
buggy results: SOME_MACRO__LINE__ BAD_PASTE( SOME_MACRO, __LINE__) PASTE( SOME_MACRO, __LINE__) desired result: function_name21
źródło
__LINE__
jest to specjalna nazwa makra, która jest zastępowana przez preprocesor z bieżącym numerem wiersza pliku źródłowego.Oto pułapka, na którą natknąłem się podczas aktualizacji do nowej wersji kompilatora:
Niepotrzebne użycie operatora wklejania tokenów (
##
) jest nieprzenośne i może generować niepożądane spacje, ostrzeżenia lub błędy.Gdy wynik operatora wklejania tokenu nie jest prawidłowym tokenem preprocesora, operator wklejania tokenu jest niepotrzebny i prawdopodobnie szkodliwy.
Na przykład można spróbować zbudować literały ciągów w czasie kompilacji przy użyciu operatora token-pasting:
#define STRINGIFY(x) #x #define PLUS(a, b) STRINGIFY(a##+##b) #define NS(a, b) STRINGIFY(a##::##b) printf("%s %s\n", PLUS(1,2), NS(std,vector));
W niektórych kompilatorach zwróci to oczekiwany wynik:
1+2 std::vector
Na innych kompilatorach będzie to zawierać niepożądane spacje:
1 + 2 std :: vector
Dość nowoczesne wersje GCC (> = 3.3 lub więcej) nie będą w stanie skompilować tego kodu:
foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token
Rozwiązaniem jest pominięcie operatora wklejania tokenów podczas łączenia tokenów preprocesora z operatorami C / C ++:
#define STRINGIFY(x) #x #define PLUS(a, b) STRINGIFY(a+b) #define NS(a, b) STRINGIFY(a::b) printf("%s %s\n", PLUS(1,2), NS(std,vector));
GCC CPP dokumentacji rozdział na łączenie jest bardziej użyteczne informacje dotyczące operatora tokenu wklejenie.
źródło
Jest to przydatne we wszystkich sytuacjach, aby nie powtarzać się niepotrzebnie. Poniżej znajduje się przykład z kodu źródłowego Emacsa. Chcielibyśmy załadować szereg funkcji z biblioteki. Należy przypisać funkcję „foo”
fn_foo
i tak dalej. Definiujemy następujące makro:#define LOAD_IMGLIB_FN(lib,func) { \ fn_##func = (void *) GetProcAddress (lib, #func); \ if (!fn_##func) return 0; \ }
Możemy go wtedy użyć:
Korzyścią jest brak konieczności zapisywania obu
fn_XpmFreeAttributes
i"XpmFreeAttributes"
(i ryzyko błędnej pisowni jednego z nich).źródło
Poprzednie pytanie dotyczące przepełnienia stosu dotyczyło płynnej metody generowania reprezentacji ciągów dla stałych wyliczenia bez częstego ponownego wpisywania, które może powodować błędy.
Połączyć
Moja odpowiedź na to pytanie pokazała, jak zastosowanie małej magii preprocesora pozwala zdefiniować wyliczenie w ten sposób (na przykład) ...;
... Z tą korzyścią, że rozwinięcie makr nie tylko definiuje wyliczenie (w pliku .h), ale także definiuje pasującą tablicę ciągów (w pliku .c);
const char *ColorStringTable[] = { "RED", "GREEN", "BLUE" };
Nazwa tabeli ciągów pochodzi z wklejenia parametru makra (tj. Color) do StringTable przy użyciu operatora ##. Takie aplikacje (sztuczki?) To te, w których operatory # i ## są nieocenione.
źródło
Możesz użyć wklejania tokenów, gdy chcesz połączyć parametry makra z czymś innym.
Może być używany do szablonów:
#define LINKED_LIST(A) struct list##_##A {\ A value; \ struct list##_##A *next; \ };
W tym przypadku LINKED_LIST (int) dałoby ci
struct list_int { int value; struct list_int *next; };
Podobnie możesz napisać szablon funkcji do przeglądania listy.
źródło
Używam go w programach C, aby pomóc poprawnie wymusić prototypy zestawu metod, które muszą być zgodne z jakąś konwencją wywoływania. W pewnym sensie można to wykorzystać do orientacji obiektu dla biednego człowieka w prostym C:
rozwija się do czegoś takiego:
STATUS activeCall_constructor( HANDLE *pInst ) STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent ); STATUS activeCall_destructor( HANDLE *pInst );
Wymusza to poprawną parametryzację dla wszystkich obiektów „pochodnych”, gdy:
powyższe w plikach nagłówkowych, itp. Jest to również przydatne przy konserwacji, jeśli zdarzy ci się nawet zmienić definicje i / lub dodać metody do "obiektów".
źródło
SGlib używa ## do prostowania szablonów w C. Ponieważ nie ma przeciążenia funkcji, ## jest używany do wklejania nazwy typu do nazw generowanych funkcji. Gdybym miał typ listy o nazwie list_t, otrzymywałbym funkcje o nazwie takiej jak sglib_list_t_concat i tak dalej.
źródło
Używam go do asercji domowej na niestandardowym kompilatorze C dla osadzonych:
#define ASSERT(exp) if(!(exp)){ \ print_to_rs232("Assert failed: " ## #exp );\ while(1){} //Let the watchdog kill us
źródło
##
?Używam go do dodawania niestandardowych przedrostków do zmiennych zdefiniowanych przez makra. Więc coś takiego:
rozwija się do:
void __testframework_test_name ()
źródło
Głównym zastosowaniem jest sytuacja, gdy masz konwencję nazewnictwa i chcesz, aby makro wykorzystywało tę konwencję nazewnictwa. Być może masz kilka rodzin metod: image_create (), image_activate () i image_release () także file_create (), file_activate (), file_release () i mobile_create (), mobile_activate () i mobile_release ().
Możesz napisać makro do obsługi cyklu życia obiektu:
#define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release())
Oczywiście coś w rodzaju „minimalnej wersji obiektów” nie jest jedynym rodzajem konwencji nazewnictwa, do którego się to odnosi - prawie większość konwencji nazewnictwa używa wspólnego podłańcucha do tworzenia nazw. Mógłby to być nazwy funkcji (jak powyżej) lub nazwy pól, nazwy zmiennych lub prawie wszystko inne.
źródło
Jedno ważne zastosowanie w WinCE:
#define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT))
Definiując opis bitów rejestru wykonujemy następujące czynności:
#define ADDR_LEFTSHIFT 0 #define ADDR_WIDTH 7
A korzystając z BITFMASK, po prostu użyj:
źródło
Jest to bardzo przydatne do logowania. Możesz to zrobić:
#define LOG(msg) log_msg(__function__, ## msg)
Lub, jeśli Twój kompilator nie obsługuje funkcji i func :
#define LOG(msg) log_msg(__file__, __line__, ## msg)
Powyższe „funkcje” rejestrują komunikat i pokazują dokładnie, która funkcja zarejestrowała komunikat.
Moja składnia C ++ może nie być całkiem poprawna.
źródło