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


88

Come accennato in molte delle mie domande precedenti, sto lavorando tramite K&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 imparare il C - è l' ##operatore del preprocessore. Secondo K&R:

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

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

così paste(name, 1)crea il token name1.

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

Risposte:


47

CrashRpt: utilizzo di ## per convertire stringhe macro multibyte in Unicode

Un utilizzo interessante in CrashRpt (libreria per la segnalazione di crash) è 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 Laccanto a __ DATE __ti darebbe un errore di compilazione.


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

Windows utilizza qualcosa di simile al seguente:

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

Ed _Tè utilizzato ovunque nel codice


Varie librerie, che utilizzano per pulire i nomi di accessor e modificatori:

L'ho anche visto usato nel codice per definire funzioni di accesso e modificatori:

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

Allo stesso modo è possibile utilizzare lo stesso metodo per qualsiasi altro tipo di creazione intelligente di nomi.


Varie librerie, utilizzandola per fare più dichiarazioni di variabili contemporaneamente:

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

3
Poiché è possibile concatenare i valori letterali di stringa in fase di compilazione, è possibile ridurre l'espressione BuildDate std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__); e creare implicitamente l'intera stringa contemporaneamente.
user666412

49

Una cosa da tenere presente quando si utilizzano gli operatori di preelaborazione token-paste (' ##') o stringizing (' #') è che è necessario utilizzare un livello aggiuntivo di riferimento indiretto affinché funzionino correttamente in tutti i casi.

Se non lo fai e gli elementi passati all'operatore di incolla di token sono macro stesse, 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__)));
}

Il risultato:

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

desired result:
function_name21

1
Per una spiegazione di questo comportamento del preprocessore, vedere stackoverflow.com/questions/8231966/…
Adam Davis

@MichaelBurr stavo leggendo la tua risposta e ho un dubbio. Come mai questa LINEA sta stampando il numero di linea?
HELP PLZ

3
@ AbhimanyuAryan: Non sono sicuro se questo è ciò che stai chiedendo, ma __LINE__è un nome di macro speciale che viene sostituito dal preprocessore con il numero di riga corrente del file di origine.
Michael Burr

Sarebbe bello se le specifiche della lingua potessero essere citate / collegate, come qui
Antonio

14

Ecco un trucco che ho riscontrato durante l'aggiornamento a una nuova versione di un compilatore:

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

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

Ad esempio, si potrebbe provare a creare stringhe letterali 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 token-paste quando si concatenano 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 GCC CPP sulla concatenazione contiene informazioni più utili sull'operatore di token-incolla.


Grazie - non ne ero a conoscenza (ma poi non uso troppo questi operatori di pre-elaborazione ...).
Michael Burr,

3
Si chiama operatore "token pasting" per una ragione: l'intento è di finire con un singolo token quando hai finito. Bella recensione.
Mark Ransom

Quando il risultato dell'operatore di incolla di token non è un token del preprocessore valido, il comportamento non è definito.
alecov

I cambiamenti di linguaggio come float esadecimali o (in C ++) separatori di cifre e valori letterali definiti dall'utente, cambiano continuamente ciò che costituisce un "token di preelaborazione valido", quindi per favore non abusarne mai in questo modo! Se devi separare i token (della lingua propria), scrivi come due token separati e non fare affidamento su interazioni accidentali tra la grammatica del preprocessore e la lingua corretta.
Kerrek SB

6

Questo è utile in tutti i tipi di situazioni per non ripetersi inutilmente. Quello che segue è un esempio dal codice sorgente di Emacs. Vorremmo caricare una serie di funzioni da una libreria. La funzione "foo" dovrebbe essere assegnata fn_fooe 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 dover scrivere entrambi fn_XpmFreeAttributese "XpmFreeAttributes"(e rischiare di sbagliare l'ortografia di uno di essi).


4

Una domanda precedente su Stack Overflow chiedeva un metodo semplice per generare rappresentazioni di stringa per le costanti di enumerazione senza un sacco di riscrittura soggetta a errori.

Link

La mia risposta a questa domanda ha mostrato come l'applicazione di una piccola magia del preprocessore ti permetta di definire la tua enumerazione in questo modo (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), 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) in StringTable utilizzando l'operatore ##. Applicazioni (trucchi?) Come questa sono dove gli operatori # e ## sono inestimabili.


3

È possibile utilizzare il token paste quando è necessario concatenare i parametri della 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 puoi scrivere un modello di funzione per l'attraversamento della lista.


2

Lo uso nei programmi C per aiutare a rafforzare 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 utilizzato per l'orientamento agli oggetti dell'uomo povero in C dritto:

SCREEN_HANDLER( activeCall )

si espande in qualcosa di simile:

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

Ciò impone la corretta parametrizzazione per tutti gli oggetti "derivati" quando si esegue:

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

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


2

SGlib usa ## fondamentalmente per confondere i modelli 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, otterrei funzioni denominate come sglib_list_t_concat e così via.


2

Lo uso per un'asserzione fatta in casa 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 



3
Immagino che tu intenda con "non standard" che il compilatore non ha incollato le stringhe ma ha incollato i token - o avrebbe funzionato anche senza ##?
PJTraill

1

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

UNITTEST(test_name)

si espande a:

void __testframework_test_name ()

1

L'utilizzo principale è quando si dispone di una convenzione di denominazione e si desidera che la macro tragga vantaggio da 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 ().

Potresti scrivere una macro per gestire il 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 degli 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. Potrebbe essere nomi di funzioni (come sopra) o nomi di campi, nomi di variabili o qualsiasi altra cosa.


1

Un utilizzo 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)

0

È molto utile per la registrazione. Tu puoi fare:

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

Oppure, se il tuo compilatore non supporta function e func :

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

Le "funzioni" precedenti registrano il messaggio e mostrano esattamente quale funzione ha registrato un messaggio.

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


1
Cosa stavi cercando di fare con quello? Funzionerebbe altrettanto bene senza "##", poiché non è necessario token-paste "," in "msg". Stavi cercando di stringere il messaggio? Inoltre, FILE e LINE devono essere in maiuscolo, non in minuscolo.
bk1e

Hai davvero ragione. Devo trovare lo script originale per vedere come è stato utilizzato ##. Peccato per me, nessun biscotto oggi!
ya23
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.