Domanda

Come menzionato in molte delle mie precedenti domande, sto lavorando tramite K & amp; R, e attualmente sono nel preprocessore. Una delle cose più interessanti - qualcosa che non avevo mai saputo prima da nessuno dei miei precedenti tentativi di apprendere C - è l'operatore preprocessore ## . Secondo K & amp; R:

  

L'operatore preprocessore ##   fornisce un modo per concatenare l'effettivo   argomenti durante l'espansione macro. Se una   parametro nel testo sostitutivo è   adiacente a un ## , il parametro è   sostituito dall'argomento reale, il    ## e lo spazio bianco circostante sono   rimosso e il risultato viene nuovamente scannerizzato.   Ad esempio, la macro incolla   concatena i suoi due argomenti:

     

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

     

così incolla (nome, 1) crea il token    nome1 .

Come e perché qualcuno dovrebbe usarlo nel mondo reale? Quali sono esempi pratici del suo uso e ci sono aspetti da considerare?

È stato utile?

Soluzione

CrashRpt: usando ## per convertire le stringhe multi-byte macro in Unicode

Un utilizzo interessante in CrashRpt (libreria per la segnalazione di 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 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 Unicode generico o stringhe multi-byte

Windows utilizza qualcosa di simile al seguente:

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

E _T è usato ovunque nel codice


Varie librerie, utilizzando per nomi di modificatori e modificatori puliti:

L'ho visto anche usato nel codice per definire accessori e modificatori:

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

Allo stesso modo puoi usare questo stesso metodo per qualsiasi altro tipo di creazione intelligente del nome.


Varie librerie, che la usano per fare 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;

Altri suggerimenti

Una cosa da tenere presente quando si utilizzano gli operatori di pre-elaborazione token-paste (' ## ') o si sta stringendo (' # ') è che devono utilizzare un ulteriore livello di riferimento indiretto affinché funzionino correttamente in tutti i casi.

Se non lo fai e gli elementi passati all'operatore di incollaggio token sono macro stessi, otterrai risultati che probabilmente non sono quelli che desideri:

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

L'output:

buggy results:
SOME_MACRO__LINE__
BAD_PASTE( SOME_MACRO, __LINE__)
PASTE( SOME_MACRO, __LINE__)

desired result:
function_name21

Ecco un gotcha in cui mi sono imbattuto durante l'aggiornamento a una nuova versione di un compilatore:

L'uso non necessario dell'operatore incolla token ( ## ) non è portatile e può generare spazi bianchi, avvisi o errori indesiderati.

Quando il risultato dell'operatore incolla token non è un token preprocessore valido, l'operatore incolla token non è necessario e può essere dannoso.

Ad esempio, si potrebbe provare a creare valori letterali di stringa in fase di compilazione utilizzando l'operatore token-paste:

#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 bianchi indesiderati:

1 + 2 std :: vector

Le versioni abbastanza moderne di GCC (> = 3.3 o giù di lì) 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 è omettere l'operatore di incollaggio token durante la concatenazione di token 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 CPP sulla concatenazione di GCC ha informazioni più utili sull'operatore di incollaggio token.

Questo è utile in tutti i tipi di situazioni per non ripetere inutilmente te stesso. Quello che segue è un esempio del codice sorgente di Emacs. Vorremmo caricare un numero di funzioni da una libreria. La funzione "pippo" dovrebbe essere assegnato 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 è di dover scrivere sia fn_XpmFreeAttributes che " XpmFreeAttributes " (e rischiare di sbagliare a scriverne uno).

Una domanda precedente su Stack & nbsp; Overflow richiedeva un metodo fluido per generare rappresentazioni di stringa per costanti di enumerazione senza molta riscrittura soggetta a errori.

Link

La mia risposta a questa domanda ha mostrato come l'applicazione della piccola magia del preprocessore ti consente di definire la tua enumerazione in questo modo (ad esempio) ...;

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

... Con il vantaggio che l'espansione macro non solo definisce l'enumerazione (in un file .h), ma definisce anche un array di stringhe corrispondente (in un file .c);

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

Il nome della tabella delle stringhe deriva dall'incollare il parametro macro (cioè Color) su StringTable utilizzando l'operatore ##. Applicazioni (trucchi?) Come questo sono dove gli operatori # e ## sono preziosi.

Lo uso nei programmi C per aiutare a far rispettare 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 l'orientamento agli oggetti del povero in 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 );

Questo impone la corretta parametrizzazione per tutti i "derivati" oggetti quando lo fai:

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

quanto sopra nei file di intestazione, ecc. È anche utile per la manutenzione se ti capita di voler modificare le definizioni e / o aggiungere metodi agli oggetti "quotati".

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

Lo uso per un'asserzione home roll 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 


Puoi usare l'incollaggio di token quando devi 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 è possibile scrivere un modello di funzione per l'attraversamento dell'elenco.

Lo uso per aggiungere prefissi personalizzati alle variabili definite dalle macro. Quindi qualcosa del tipo:

UNITTEST(test_name)

si espande in:

void __testframework_test_name ()

L'uso principale è quando si dispone di una convenzione di denominazione e si desidera che la macro sfrutti tale convenzione di denominazione. Forse hai 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 ().

È possibile 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())

Naturalmente, una sorta di "versione minima di oggetti" non è l'unico tipo di convenzione di denominazione a cui si applica - 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), o nomi di campi, nomi di variabili o quasi ogni 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 registro, facciamo quanto segue:

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

E mentre usi BITFMASK, usa semplicemente:

BITFMASK(ADDR)

È molto utile per la registrazione. Puoi fare:

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

In alternativa, se il compilatore non supporta la funzione e func :

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

Le precedenti funzioni "quotate" registra il messaggio e mostra esattamente quale funzione ha registrato un messaggio.

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top