Come creare una macro variadica (numero variabile di argomenti)


196

Voglio scrivere una macro in C che accetta un numero qualsiasi di parametri, non un numero specifico

esempio:

#define macro( X )  something_complicated( whatever( X ) )

dove Xc'è un numero qualsiasi di parametri

Ne ho bisogno perché whateverè sovraccarico e può essere chiamato con 2 o 4 parametri.

Ho provato a definire la macro due volte, ma la seconda definizione ha sovrascritto la prima!

Il compilatore con cui sto lavorando è g ++ (più specificamente, mingw)


8
Vuoi C o C ++? Se stai usando C, perché stai compilando con un compilatore C ++? Per utilizzare le macro variadic C99 appropriate, è necessario compilare un compilatore C che supporti C99 (come gcc), non un compilatore C ++, poiché C ++ non ha macro variadiche standard.
— Chris Lutz,

Bene, ho pensato che C ++ sia un super set di C in questo senso ..
— Hasen

tigcc.ticalc.org/doc/cpp.html#SEC13 ha una spiegazione dettagliata delle macro variadiche.
— Gnubie,

Una buona spiegazione ed esempio è qui http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
— zafarulq

3
Per i futuri lettori: C non è un sottoinsieme di C ++. Condividono molte cose, ma ci sono regole che impediscono loro di essere sottoinsieme e superset reciproche.
— Pharap,

Risposte:


295

Modo C99, supportato anche dal compilatore VC ++.

#define FOO(fmt, ...) printf(fmt, ##__VA_ARGS__)

8
Non credo che C99 richieda il ## prima di VA_ARGS . Potrebbe essere VC ++.
— Chris Lutz,

98
Il motivo per ## prima di VA_ARGS è che ingoia la virgola precedente nel caso in cui l'elenco di argomenti variabili sia vuoto, ad es. FOO ("a") si espande in printf ("a"). Questa è un'estensione di gcc (e vc ++, forse), C99 richiede che almeno un argomento sia presente al posto dell'ellissi.
— jpalecek,

110
##non è necessario e non è portatile. #define FOO(...) printf(__VA_ARGS__)fa il lavoro in modo portatile; il fmtparametro può essere omesso dalla definizione.
— alecov,

4
IIRC, il ## è specifico per GCC e consente il passaggio di zero parametri
— Mawg dice di ripristinare Monica il

10
La sintassi ## - funziona anche con llvm / clang e il compilatore di Visual Studio. Quindi potrebbe non essere portatile, ma è supportato dai principali compilatori.
— K. Biermann,

37

__VA_ARGS__è il modo standard per farlo. Non utilizzare hack specifici del compilatore se non è necessario.

Sono davvero seccato di non poter commentare il post originale. In ogni caso, C ++ non è un superset di C. È davvero stupido compilare il codice C con un compilatore C ++. Non fare quello che non fa Donny.


8
"È davvero sciocco compilare il tuo codice C con un compilatore C ++" => Non considerato da tutti (incluso me). Vedere ad esempio le linee guida di base su C ++: CPL.1: Preferire da C ++ a C , CPL.2: se è necessario utilizzare C, utilizzare il sottoinsieme comune di C e C ++ e compilare il codice C come C ++ . Sono indeciso a pensare a quali "soli ismi-C" è davvero necessario per far valere la pena di non programmare nel sottoinsieme compatibile, e i comitati C e C ++ hanno lavorato duramente per rendere disponibile quel sottoinsieme compatibile.
— HostileFork afferma di non fidarsi di SE

4
@HostileFork Abbastanza giusto, anche se ovviamente la gente del C ++ vorrebbe incoraggiare l'uso del C ++. Altri non sono d'accordo, però; Linux Torvalds, ad esempio, ha apparentemente respinto diverse proposte di patch del kernel Linux che tentano di sostituire l'identificatore classcon klassper consentire la compilazione con un compilatore C ++. Nota anche che ci sono alcune differenze che ti faranno inciampare; ad esempio, l'operatore ternario non viene valutato allo stesso modo in entrambe le lingue e la inlineparola chiave significa qualcosa di completamente diverso (come ho appreso da una domanda diversa).
— Kyle Strand,

3
Per progetti di sistemi davvero multipiattaforma come un sistema operativo, vuoi davvero aderire alla rigida C, perché i compilatori C sono molto più comuni. Nei sistemi embedded, ci sono ancora piattaforme senza compilatori C ++. (Ci sono piattaforme con solo compilatori C passabili!) I compilatori C ++ mi rendono nervoso, in particolare per i sistemi cyber-fisici, e immagino che non sono l'unico programmatore software / C incorporato con quella sensazione.
— ribasso

2
@downbeat Sia che usi C ++ per la produzione o meno, se sei preoccupato per il rigore, essere in grado di compilare con C ++ ti dà poteri magici per l'analisi statica. Se hai una query che vuoi fare di una base di codice C ... chiedendoti se alcuni tipi vengono usati in determinati modi, imparare come usare type_traits può costruire strumenti mirati per questo. Quello che avresti pagato un sacco di soldi per uno strumento di analisi statica di C da fare può essere fatto con un po 'di C ++ sapere come e il compilatore che hai già ...
— HostileFork dice di non fidarti di SE

1
Sto parlando alla domanda di Linux. (Ho appena notato che dice "Linux Torvalds" ha!)
— battuto il

28

Non credo sia possibile, potresti fingere con doppie parentesi ... solo se non hai bisogno degli argomenti individualmente.

#define macro(ARGS) some_complicated (whatever ARGS)
// ...
macro((a,b,c))
macro((d,e))

21
Mentre è possibile avere una macro variadica, usare una doppia parentesi è un buon consiglio.
— David Rodríguez - dribeas,

2
Il compilatore XC di Microchip non supporta le macro variadiche, quindi questo trucco a doppia parentesi è il meglio che puoi fare.
— gbmhunter,

10
#define DEBUG

#ifdef DEBUG
  #define PRINT print
#else
  #define PRINT(...) ((void)0) //strip out PRINT instructions from code
#endif 

void print(const char *fmt, ...) {

    va_list args;
    va_start(args, fmt);
    vsprintf(str, fmt, args);
        va_end(args);

        printf("%s\n", str);

}

int main() {
   PRINT("[%s %d, %d] Hello World", "March", 26, 2009);
   return 0;
}

Se il compilatore non comprende le macro variadiche, è anche possibile rimuovere PRINT con uno dei seguenti:

#define PRINT //

o

#define PRINT if(0)print

Il primo commenta le istruzioni PRINT, il secondo impedisce l'istruzione PRINT a causa di una condizione NULL if. Se l'ottimizzazione è impostata, il compilatore dovrebbe eliminare le istruzioni mai eseguite come: if (0) print ("ciao mondo"); oppure ((vuoto) 0);


8
#define PRINT // non sostituirà PRINT con //
— bitc

8
#define PRINT if (0) print non è una buona idea o perché il codice chiamante potrebbe avere il suo altro -se per chiamare PRINT. Meglio è: #define PRINT if (true); else print
— bitc

3
Lo standard "non fare nulla, con grazia" è do {} while (0)
— vonbrand,

La ifversione corretta di "non farlo" che tiene conto della struttura del codice è: if (0) { your_code } elseun punto e virgola dopo che l'espansione della macro termina il else. La whileversione è simile al seguente: while(0) { your_code } il problema con la do..whileversione è che il codice in do { your_code } while (0)viene eseguito una volta, garantito. In tutti e tre i casi, se your_codeè vuoto, è corretto do nothing gracefully.
— Jesse Chisholm,

4

spiegato per g ++ qui, anche se fa parte di C99, quindi dovrebbe funzionare per tutti

http://www.delorie.com/gnu/docs/gcc/gcc_44.html

esempio rapido:

#define debug(format, args...) fprintf (stderr, format, args)

3
Le macro variadiche di GCC non sono macro variadiche C99. GCC ha macro variadiche C99, ma G ++ non le supporta, poiché C99 non fa parte di C ++.
— Chris Lutz,

1
In realtà g ++ compilerà macro C99 in file C ++. Emetterà un avviso, tuttavia, se compilato con '-pedantic'.
— Alex B,

2
Non è C99. C99 usa la macro VA_ARGS ).
— qrdl

1
C ++ 11 supporta anche __VA_ARGS__, sebbene siano supportati anche dai compilatori nelle versioni precedenti, come estensione.
— Ethouris,

1
Questo non funziona per printf ("hi"); dove non ci sono var args. Qualche modo generico per risolvere questo problema?
— BTR Naidu,
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.