Quali sono le applicazioni dell’operatore del ## preprocessore e i trucchi da considerare?

Come accennato in molte delle mie precedenti domande, sto lavorando a K & R e sono attualmente nel preprocessore. Una delle cose più interessanti – qualcosa che non ho mai saputo prima da nessuno dei miei precedenti tentativi di imparare C – è l’operatore di ## preprocessore. Secondo K & R:

L’operatore del preprocessore ## fornisce un modo per concatenare argomenti reali durante l’espansione della macro. Se un parametro nel testo sostitutivo è adiacente a un ## , il parametro viene sostituito dall’argomento effettivo, il ## e lo spazio bianco circostante vengono rimossi e il risultato viene riesaminato. Ad esempio, la macro paste concatena i suoi due argomenti:

#define paste(front, back) front ## back

so paste(name, 1) crea il name1 .

Come e perché qualcuno dovrebbe usarlo nel mondo reale? Quali sono esempi pratici del suo uso e ci sono trucchi da prendere in considerazione?

    CrashRpt: utilizzo di ## per convertire stringhe multi-byte macro in Unicode

    Un utilizzo interessante in CrashRpt (libreria di segnalazione degli arresti anomali) è il seguente:

     #define WIDEN2(x) L ## x #define WIDEN(x) WIDEN2(x) //Note you need a WIDEN2 so that __DATE__ will evaluate first. 

    Qui vogliono usare una stringa di due byte invece di una stringa di un byte per ogni carattere. Questo probabilmente sembra davvero inutile, ma lo fanno per una buona ragione.

      std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__); 

    Lo usano con un’altra macro che restituisce una stringa con la data e l’ora.

    Mettere L accanto a un __ DATE __ ti darebbe un errore di compilazione.


    Windows: utilizzo di ## per stringhe Unicode o multibyte generiche

    Windows utilizza qualcosa come il seguente:

     #ifdef _UNICODE #define _T(x) L ## x #else #define _T(x) x #endif 

    E _T è usato ovunque nel codice


    Varie librerie, che usano per accessor clean e nomi modificatori:

    Ho anche visto che è usato nel codice per definire accessors e modificatori:

     #define MYLIB_ACCESSOR(name) (Get##name) #define MYLIB_MODIFIER(name) (Set##name) 

    Allo stesso modo è ansible utilizzare questo stesso metodo per qualsiasi altro tipo di creazione intelligente del nome.


    Varie librerie, utilizzandolo per rendere più dichiarazioni variabili contemporaneamente:

     #define CREATE_3_VARS(name) name##1, name##2, name##3 int CREATE_3_VARS(myInts); myInts1 = 13; myInts2 = 19; myInts3 = 77; 

    Una cosa a cui prestare attenzione quando si utilizzano gli operatori di pre-elaborazione token-paste (‘ ## ‘) o stringizing (‘ # ‘) è che si deve usare un livello aggiuntivo di riferimento indiretto perché funzionino correttamente in tutti i casi.

    Se non lo fai e gli elementi passati all’operatore che incolla i token sono essi stessi delle macro, otterrai risultati che probabilmente non sono ciò che desideri:

     #include  #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__))); } 

    Il risultato:

     buggy results: SOME_MACRO__LINE__ BAD_PASTE( SOME_MACRO, __LINE__) PASTE( SOME_MACRO, __LINE__) desired result: function_name21 

    Ecco un trucco che ho incontrato durante l’aggiornamento a una nuova versione di un compilatore:

    L’uso non necessario dell’operatore di token-pasting ( ## ) non è portabile e può generare indesiderati spazi, avvisi o errori.

    Quando il risultato dell’operatore di token-pasting non è un token di preprocessore valido, l’operatore di token-pasting non è necessario e probabilmente dannoso.

    Ad esempio, si potrebbe provare a creare letterali stringa in fase di compilazione usando l’operatore di 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)); 

    Su alcuni compilatori, questo produrrà il risultato atteso:

     1+2 std::vector 

    Su altri compilatori, questo includerà spazi vuoti indesiderati:

     1 + 2 std :: vector 

    Le versioni abbastanza moderne di GCC (> = 3.3 o così) non riusciranno a compilare questo codice:

     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 

    La soluzione è di omettere l’operatore di token-pasting quando concatena i token del preprocessore agli operatori 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)); 

    Il capitolo della documentazione sulla CPP di GCC sulla concatenazione contiene informazioni più utili sull’operatore che incolla i token.

    Questo è utile in tutti i tipi di situazioni per non ripetersi inutilmente. Quello che segue è un esempio dal codice sorgente di Emacs. Vorremmo caricare un numero di funzioni da una libreria. La funzione “pippo” dovrebbe essere assegnata a fn_foo e così via. Definiamo la seguente macro:

     #define LOAD_IMGLIB_FN(lib,func) { \ fn_##func = (void *) GetProcAddress (lib, #func); \ if (!fn_##func) return 0; \ } 

    Possiamo quindi usarlo:

     LOAD_IMGLIB_FN (library, XpmFreeAttributes); LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer); LOAD_IMGLIB_FN (library, XpmReadFileToImage); LOAD_IMGLIB_FN (library, XImageFree); 

    Il vantaggio non sta nel dover scrivere sia fn_XpmFreeAttributes sia "XpmFreeAttributes" (e rischiare di sbagliare a "XpmFreeAttributes" uno di essi).

    Una precedente domanda su Stack Overflow richiedeva un metodo semplice per generare rappresentazioni di stringhe per le costanti di enumerazione senza molta riscrittura soggetta a errori.

    collegamento

    La mia risposta a questa domanda ha mostrato come applicare la piccola magia del preprocessore ti consente di definire la tua enumerazione come questa (per esempio) …;

     ENUM_BEGIN( Color ) ENUM(RED), ENUM(GREEN), ENUM(BLUE) ENUM_END( Color ) 

    … Con il vantaggio che l’espansione della macro non solo definisce l’enumerazione (in un file .h), definisce anche una matrice di stringhe corrispondente (in un file .c);

     const char *ColorStringTable[] = { "RED", "GREEN", "BLUE" }; 

    Il nome della tabella di stringhe proviene dal incollare il parametro macro (es. Colore) a StringTable usando l’operatore ##. Applicazioni (trucchi?) Come questo sono dove gli operatori # e ## sono inestimabili.

    È ansible utilizzare il token che incolla quando è necessario concatenare i parametri macro con qualcos’altro.

    Può essere utilizzato per i modelli:

     #define LINKED_LIST(A) struct list##_##A {\ A value; \ struct list##_##A *next; \ }; 

    In questo caso LINKED_LIST (int) ti darebbe

     struct list_int { int value; struct list_int *next; }; 

    Allo stesso modo è ansible scrivere un modello di funzione per l’attraversamento di liste.

    Lo uso nei programmi C per aiutare a far applicare correttamente i prototipi per un insieme di metodi che devono essere conformi a una sorta di convenzione di chiamata. In un certo senso, questo può essere usato per orientare l’object del povero nel dritto C:

     SCREEN_HANDLER( activeCall ) 

    si espande in qualcosa del genere:

     STATUS activeCall_constructor( HANDLE *pInst ) STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent ); STATUS activeCall_destructor( HANDLE *pInst ); 

    Ciò impone la parametrizzazione corretta per tutti gli oggetti “derivati” quando lo fai:

     SCREEN_HANDLER( activeCall ) SCREEN_HANDLER( ringingCall ) SCREEN_HANDLER( heldCall ) 

    quanto sopra nei file di intestazione, ecc. È utile anche per la manutenzione se si desidera modificare le definizioni e / o aggiungere metodi agli “oggetti”.

    SGlib usa ## per fondere i template in C. Perché non c’è sovraccarico di funzioni, ## è usato per incollare il nome del tipo nei nomi delle funzioni generate. Se avessi un tipo di lista chiamato list_t, allora otterrei funzioni denominate come sglib_list_t_concat e così via.

    Lo uso per un asser del laminato a domicilio su un compilatore C non standard per embedded:

    #define ASSERT(exp) if(!(exp)){ \ print_to_rs232("Assert failed: " ## #exp );\ while(1){} //Let the watchdog kill us
    #define ASSERT(exp) if(!(exp)){ \ print_to_rs232("Assert failed: " ## #exp );\ while(1){} //Let the watchdog kill us 

    Lo uso per aggiungere prefissi personalizzati a variabili definite da macro. Quindi qualcosa come:

     UNITTEST(test_name) 

    si espande a:

     void __testframework_test_name () 

    L’uso principale è quando si ha una convenzione di denominazione e si desidera che la macro utilizzi tale convenzione di denominazione. Forse avete diverse famiglie di metodi: image_create (), image_activate () e image_release () anche file_create (), file_activate (), file_release () e mobile_create (), mobile_activate () e mobile_release ().

    È ansible scrivere una macro per la gestione del ciclo di vita degli oggetti:

     #define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release()) 

    Certamente, una sorta di “versione minima degli oggetti” non è l’unico tipo di convenzione di denominazione applicabile: quasi la stragrande maggioranza delle convenzioni di denominazione fa uso di una sottostringa comune per formare i nomi. Potrei farmi nomi di funzioni (come sopra), nomi di campi, nomi di variabili o qualsiasi altra cosa.

    Un uso importante in WinCE:

     #define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT)) 

    Durante la definizione della descrizione del bit di registrazione, facciamo quanto segue:

     #define ADDR_LEFTSHIFT 0 #define ADDR_WIDTH 7 

    E mentre usi BITFMASK, usa semplicemente:

     BITFMASK(ADDR) 

    È molto utile per la registrazione. Tu puoi fare:

     #define LOG(msg) log_msg(__function__, ## msg) 

    Oppure, se il tuo compilatore non supporta la funzione e la funzione :

     #define LOG(msg) log_msg(__file__, __line__, ## msg) 

    Le “funzioni” sopra registrano il messaggio e mostrano esattamente quale funzione ha registrato un messaggio.

    La mia syntax C ++ potrebbe non essere del tutto corretta.