Ricerca…


introduzione

Le macro X sono una tecnica basata sul preprocessore per ridurre al minimo il codice ripetitivo e mantenere le corrispondenze di dati / codice. Più espansioni di macro distinte basate su un insieme comune di dati sono supportate rappresentando l'intero gruppo di espansioni tramite una singola macro principale, con il testo di sostituzione di quella macro costituito da una sequenza di espansioni di una macro interna, una per ciascun dato. La macro interna è tradizionalmente chiamata X() , da cui il nome della tecnica.

Osservazioni

Si prevede che l'utente di una macro master stile X-macro fornisca la propria definizione per la macro X() interna e, nell'ambito della sua estensione, per espandere la macro master. I riferimenti macro interni del master vengono quindi espansi in base alla definizione dell'utente di X() . In questo modo, la quantità di codice boilerplate ripetitivo nel file sorgente può essere ridotta (appare solo una volta, nel testo sostitutivo di X() ), come favorito dagli aderenti alla filosofia "Do not Repeat Yourself" (DRY).

Inoltre, ridefinendo X() ed espandendo la macro master una o più volte aggiuntive, le macro X possono facilitare il mantenimento di dati e codice corrispondenti: una espansione della macro dichiara i dati (come elementi di array o membri di enum, ad esempio), e le altre espansioni producono codice corrispondente.

Sebbene il nome "X-macro" derivi dal nome tradizionale della macro interna, la tecnica non dipende da quel particolare nome. Qualsiasi nome di macro valido può essere utilizzato al suo posto.

Le critiche includono

  • i file sorgente che si basano su macro X sono più difficili da leggere;
  • come tutte le macro, le macro X sono rigorosamente testuali - non forniscono intrinsecamente alcun tipo di sicurezza; e
  • Le macro X forniscono la generazione del codice . Rispetto alle alternative basate sulle funzioni di chiamata, le macro X rendono efficace il codice più grande.

Una buona spiegazione dei macro X può essere trovata nell'articolo [X-Macros] di Randy Meyers nel Dr. Dobbs ( http://www.drdobbs.com/the-new-cx-macros/184401387) .

Uso banale di X-macros per printfs

/* define a list of preprocessor tokens on which to call X */
#define X_123 X(1) X(2) X(3)

/* define X to use */
#define X(val) printf("X(%d) made this print\n", val);
X_123
#undef X
/* good practice to undef X to facilitate reuse later on */

In questo esempio il preprocessore genererà il seguente codice:

printf("X(%d) made this print\n", 1);
printf("X(%d) made this print\n", 2);
printf("X(%d) made this print\n", 3);

Valore enumerativo e identificativo

/* declare items of the enum */
#define FOREACH \
      X(item1) \
      X(item2) \
      X(item3) \
/* end of list */

/* define the enum values */
#define X(id) MyEnum_ ## id,
enum MyEnum { FOREACH };
#undef X

/* convert an enum value to its identifier */
const char * enum2string(int enumValue)
{
    const char* stringValue = NULL;
#define X(id) if (enumValue == MyEnum_ ## id) stringValue = #id;
    FOREACH
#undef X
    return stringValue;
}

Successivamente puoi usare il valore enumerato nel tuo codice e stampare facilmente il suo identificatore usando:

printf("%s\n", enum2string(MyEnum_item2));

Estensione: assegna la macro X come argomento

L'approccio X-macro può essere generalizzato un po 'rendendo il nome della macro "X" un argomento della macro master. Questo ha il vantaggio di aiutare a evitare le collisioni dei nomi di macro e di consentire l'uso di una macro generica come la macro "X".

Come sempre con le macro X, la macro master rappresenta un elenco di elementi il ​​cui significato è specifico per quella macro. In questa variante, una tale macro potrebbe essere definita in questo modo:

/* declare list of items */
#define ITEM_LIST(X) \
      X(item1) \
      X(item2) \
      X(item3) \
/* end of list */

Si potrebbe quindi generare codice per stampare i nomi degli oggetti in questo modo:

/* define macro to apply */
#define PRINTSTRING(value) printf( #value "\n");

/* apply macro to the list of items */
ITEM_LIST(PRINTSTRING)

Questo si espande a questo codice:

printf( "item1" "\n"); printf( "item2" "\n"); printf( "item3" "\n");

In contrasto con le macro X standard, dove il nome "X" è una caratteristica incorporata della macro master, con questo stile potrebbe non essere necessario o addirittura non desiderabile dopo aver indefinito la macro utilizzata come argomento ( PRINTSTRING in questo esempio).

Generazione del codice

X-Macros può essere utilizzato per la generazione di codice, scrivendo codice ripetitivo: iterare su un elenco per eseguire alcune attività o per dichiarare un insieme di costanti, oggetti o funzioni.

Qui usiamo X-macros per dichiarare un enum contenente 4 comandi e una mappa dei loro nomi come stringhe

Quindi possiamo stampare i valori stringa dell'enum.

/* All our commands */
#define COMMANDS(OP) OP(Open) OP(Close) OP(Save) OP(Quit)

/* generate the enum Commands: {cmdOpen, cmdClose, cmdSave, cmdQuit, }; */
#define ENUM_NAME(name) cmd##name,
enum Commands {
  COMMANDS(ENUM_NAME)
};
#undef ENUM_NAME

/* generate the string table */
#define COMMAND_OP(name) #name,
const char* const commandNames[] = {
  COMMANDS(COMMAND_OP)
};
#undef COMMAND_OP

/* the following prints "Quit\n": */
printf("%s\n", commandNames[cmdQuit]());

Allo stesso modo, possiamo generare una tabella di salto per chiamare le funzioni in base al valore enum.

Ciò richiede che tutte le funzioni abbiano la stessa firma. Se non accettano argomenti e restituiscono un int, lo inseriremo in un'intestazione con la definizione enum:

/* declare all functions as extern */
#define EXTERN_FUNC(name) extern int doCmd##name(void);
COMMANDS(EXTERN_FUNC)
#undef EXTERN_FUNC

/* declare the function pointer type and the jump table  */
typedef int (*CommandFunc)(void);
extern CommandFunc commandJumpTable[];

Tutti i seguenti possono essere in diverse unità di compilazione assumendo che la parte sopra sia inclusa come intestazione:

/* generate the jump table */
#define FUNC_NAME(name) doCmd##name,
CommandFunc commandJumpTable[] = {
  COMMANDS(FUNC_NAME)
};
#undef FUNC_NAME

/* call the save command like this: */
int result = commandJumpTable[cmdSave]();

/* somewhere else, we need the implementations of the commands */
int doCmdOpen(void) {/* code performing open command */}
int doCmdClose(void) {/* code performing close command */}
int doCmdSave(void) {/* code performing save command */}
int doCmdQuit(void) {/* code performing quit command */}

Un esempio di questa tecnica utilizzata nel codice reale è l' invio di comandi GPU in Chromium .



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow