Comment faire une macro variadique (nombre variable d'arguments)

196

Je veux écrire une macro en C qui accepte n'importe quel nombre de paramètres, pas un nombre spécifique

exemple:

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

Xest un nombre quelconque de paramètres

J'en ai besoin car il whateverest surchargé et peut être appelé avec 2 ou 4 paramètres.

J'ai essayé de définir la macro deux fois, mais la deuxième définition a remplacé la première!

Le compilateur avec lequel je travaille est g ++ (plus spécifiquement, mingw)

hasen
la source
8
Voulez-vous C ou C ++? Si vous utilisez C, pourquoi compilez-vous avec un compilateur C ++? Pour utiliser les macros variadiques C99 appropriées, vous devez compiler avec un compilateur C qui prend en charge C99 (comme gcc), pas un compilateur C ++, car C ++ n'a pas de macros variadiques standard.
Chris Lutz
Eh bien, je suppose que C ++ est un super ensemble de C à cet égard ..
hasen
tigcc.ticalc.org/doc/cpp.html#SEC13 a une explication détaillée des macros variadiques.
Gnubie
Une bonne explication et un exemple sont ici http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
zafarulq
3
Pour les futurs lecteurs: C n'est pas un sous-test de C ++. Ils partagent beaucoup de choses, mais il existe des règles qui les empêchent d'être des sous-ensembles et des sur-ensembles les uns des autres.
Pharap

Réponses:

295

C99, également pris en charge par le compilateur VC ++.

#define FOO(fmt, ...) printf(fmt, ##__VA_ARGS__)
Alex B
la source
8
Je ne pense pas que C99 nécessite le ## avant VA_ARGS . Cela pourrait simplement être VC ++.
Chris Lutz
97
La raison de ## avant VA_ARGS est qu'il avale la virgule précédente dans le cas où la liste d'arguments variables est vide, par exemple. FOO ("a") se développe en printf ("a"). Ceci est une extension de gcc (et vc ++, peut-être), C99 nécessite au moins un argument pour être présent à la place des points de suspension.
jpalecek
108
##n'est pas nécessaire et n'est pas portable. #define FOO(...) printf(__VA_ARGS__)fait le travail de manière portable; le fmtparamètre peut être omis de la définition.
alecov
4
IIRC, le ## est spécifique au GCC et permet le passage de paramètres nuls
Mawg dit de rétablir Monica
10
La syntaxe ## - fonctionne également avec llvm / clang et le compilateur Visual Studio. Il n'est donc peut-être pas portable, mais il est pris en charge par les principaux compilateurs.
K. Biermann
37

__VA_ARGS__est la façon standard de le faire. N'utilisez pas de hacks spécifiques au compilateur si vous n'en avez pas besoin.

Je suis vraiment ennuyé de ne pas pouvoir commenter le message d'origine. Dans tous les cas, C ++ n'est pas un surensemble de C. Il est vraiment idiot de compiler votre code C avec un compilateur C ++. Ne fais pas ce que Donny ne fait pas.

cmccabe
la source
8
"C'est vraiment idiot de compiler votre code C avec un compilateur C ++" => Pas considéré par tout le monde (y compris moi). Voir par exemple les directives de base C ++: CPL.1: préférez C ++ à C , CPL.2: si vous devez utiliser C, utilisez le sous-ensemble commun de C et C ++, et compilez le code C en C ++ . J'ai du mal à penser à ce que les "ismes C uniquement" nécessitent vraiment pour ne pas être programmés dans le sous-ensemble compatible, et les comités C et C ++ ont travaillé dur pour rendre ce sous-ensemble compatible disponible.
HostileFork dit de ne pas faire confiance au SE
4
@HostileFork Assez bien, même si bien sûr les gens de C ++ aimeraient encourager l'utilisation de C ++. Cependant, d'autres ne sont pas d'accord; Linux Torvalds, par exemple, a apparemment rejeté plusieurs correctifs proposés pour le noyau Linux qui tentent de remplacer l'identifiant classpar klasspour permettre la compilation avec un compilateur C ++. Notez également qu'il existe certaines différences qui vous feront trébucher; par exemple, l'opérateur ternaire n'est pas évalué de la même manière dans les deux langues, et le inlinemot - clé signifie quelque chose de complètement différent (comme je l'ai appris d'une question différente).
Kyle Strand
3
Pour les projets de systèmes vraiment multi-plateformes comme un système d'exploitation, vous voulez vraiment adhérer au C strict, car les compilateurs C sont beaucoup plus courants. Dans les systèmes embarqués, il existe encore des plates-formes sans compilateurs C ++. (Il existe des plates-formes avec uniquement des compilateurs C passables!) Les compilateurs C ++ me rendent nerveux, en particulier pour les systèmes cyber-physiques, et je suppose que je ne suis pas le seul programmeur de logiciels / C embarqués avec ce sentiment.
mauvais temps
2
@downbeat Que vous utilisiez C ++ pour la production ou non, si c'est la rigueur qui vous préoccupe, alors être capable de compiler avec C ++ vous donne des pouvoirs magiques pour l'analyse statique. Si vous avez une requête à faire sur une base de code C ... vous vous demandez si certains types sont utilisés de certaines façons, apprendre à utiliser type_traits peut créer des outils ciblés pour cela. Ce que vous paieriez beaucoup pour un outil d'analyse statique de C à faire peut être fait avec un peu de savoir-faire C ++ et le compilateur que vous avez déjà ...
HostileFork dit de ne pas faire confiance au SE
1
Je parle de la question de Linux. (Je viens de remarquer qu'il dit "Linux Torvalds" ha!)
Downbeat
28

Je ne pense pas que ce soit possible, vous pouvez le simuler avec des doubles parenthèses ... tant que vous n'avez pas besoin des arguments individuellement.

#define macro(ARGS) some_complicated (whatever ARGS)
// ...
macro((a,b,c))
macro((d,e))
eduffy
la source
21
Bien qu'il soit possible d'avoir une macro variadique, l'utilisation de la double parenthèse est un bon conseil.
David Rodríguez - dribeas
2
Le compilateur XC de Microchip ne prend pas en charge les macros variadiques, et donc cette astuce à double parenthèse est la meilleure que vous puissiez faire.
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;
}

Si le compilateur ne comprend pas les macros variadiques, vous pouvez également supprimer PRINT avec l'une des options suivantes:

#define PRINT //

ou

#define PRINT if(0)print

Le premier commente les instructions PRINT, le second empêche l'instruction PRINT en raison d'une condition NULL if. Si l'optimisation est définie, le compilateur doit supprimer les instructions jamais exécutées comme: if (0) print ("hello world"); ou ((nul) 0);

jpalecek
la source
8
#define PRINT // ne remplacera pas PRINT par //
bitc
8
#define PRINT if (0) print n'est pas une bonne idée non plus car le code appelant peut avoir son propre else-if pour appeler PRINT. Mieux vaut: #define PRINT if (true); else print
bitc
3
Le standard "ne rien faire, gracieusement" est do {} while (0)
vonbrand
La ifversion appropriée de «ne pas faire cela» qui prend en compte la structure du code est la suivante: if (0) { your_code } elseun point-virgule après la fin de l'expansion de votre macro else. La whileversion ressemble à: while(0) { your_code } Le problème avec la do..whileversion est que le code do { your_code } while (0)est fait une fois, c'est garanti. Dans les trois cas, si your_codeest vide, c'est un bon do nothing gracefully.
Jesse Chisholm
4

expliqué pour g ++ ici, bien qu'il fasse partie de C99 donc devrait fonctionner pour tout le monde

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

exemple rapide:

#define debug(format, args...) fprintf (stderr, format, args)
DarenW
la source
3
Les macros variadiques de GCC ne sont pas des macros variadiques C99. GCC a des macros variadiques C99, mais G ++ ne les prend pas en charge, car C99 ne fait pas partie de C ++.
Chris Lutz
1
En fait, g ++ compilera les macros C99 dans des fichiers C ++. Il émettra cependant un avertissement s'il est compilé avec '-pedantic'.
Alex B
2
Ce n'est pas C99. C99 utilise la macro VA_ARGS ).
qrdl
1
C ++ 11 prend également en charge __VA_ARGS__, bien qu'il soit également pris en charge par les compilateurs dans les versions antérieures, en tant qu'extension.
Ethouris
1
Cela ne fonctionne pas pour printf ("hi"); où il n'y a pas d'arguments var. Une façon générique de résoudre ce problème?
BTR Naidu