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.