如何制作可变参数宏(可变数量的参数)


196

我想用C编写一个宏,它接受任意数量的参数,而不是特定数量的参数

例:

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

X参数的数目在哪里

我需要这个,因为它whatever已重载,可以使用2或4个参数调用。

我尝试两次定义宏,但是第二个定义覆盖了第一个!

我正在使用的编译器是g ++(更具体地说,是mingw)


8
您要使用C还是C ++?如果您使用的是C,为什么要使用C ++编译器进行编译?要使用正确的C99可变参数宏,您应该使用支持C99的C编译器(例如gcc)而不是C ++编译器进行编译,因为C ++没有标准的可变参数宏。
克里斯·卢茨

好吧,我认为C ++在这方面是C的超集..
Hasen

tigcc.ticalc.org/doc/cpp.html#SEC13详细介绍了可变参数宏。
Gnubie 2011年

一个很好的解释和例子在这里http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
zafarulq

3
对于未来的读者:C 不是 C ++的精华。它们共享许多东西,但是有规则禁止它们成为彼此的子集和超集。
老王

Answers:


295

C99方式,VC ++编译器也支持。

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

8
我认为C99在VA_ARGS之前不需要 ## 。那可能只是VC ++。
克里斯·卢茨

97
VA_ARGS之前使用##的原因是,在变量参数列表为空的情况下,它会吞下前面的逗号。FOO(“ a”)扩展为printf(“ a”)。这是gcc(和vc ++,可能是)的扩展,C99要求至少存在一个参数来代替省略号。
jpalecek

108
##是不需要的,也不是便携式的。#define FOO(...) printf(__VA_ARGS__)以便携式方式完成工作;该fmt参数可以从定义中省略。
alecov 2012年

4
IIRC,##是GCC专用的,允许传递零参数
Mawg说,请恢复Monica 2012年

10
##语法也可用于llvm / clang和Visual Studio编译器。因此它可能不是可移植的,但是主要的编译器都支持它。
K. Biermann 2014年

37

__VA_ARGS__是这样做的标准方法。不必使用编译器特有的技巧。

我很生气,无法对原始帖子发表评论。在任何情况下,C ++都不是C的超集。使用C ++编译器编译C代码确实很愚蠢。不要做Donny不要做。


8
“用C ++编译器编译C代码真的很愚蠢” =>每个人(包括我)都没有这样考虑。例如,请参阅C ++核心准则: CPL.1:C优先于CCPL.2:如果必须使用C,请使用C和C ++的公共子集,并将C代码编译为C ++。我很难想到一个真正的“仅C语言”,要使其不值得在兼容子集中进行编程,C和C ++委员会一直在努力使该兼容子集可用。
HostileFork表示不要相信

4
@HostileFork相当公平,尽管C ++人士当然会鼓励使用C ++。但是,其他人确实不同意。Linux的Torvalds的,例如,显然已经拒绝了多次提出的Linux内核补丁试图更换标识classklass允许与C ++编译器编译。另请注意,有些差异会使您绊倒。例如,两种语言对三元运算符的评价方式inline不同,关键字的含义完全不同(正如我从另一个问题中学到的那样)。
凯尔·斯特兰德

3
对于真正的跨平台系统项目(如操作系统),您确实要坚持使用严格的C语言,因为C编译器更为常见。在嵌入式系统中,仍然存在没有C ++编译器的平台。(有些平台只有合格的C编译器!)C ++编译器让我感到紧张,尤其是对于网络物理系统,我想我不是唯一有这种感觉的嵌入式软件/ C程序员。
令人沮丧的

2
@downbeat无论您是否使用C ++进行生产,如果您担心它的严格性,那么能够使用C ++进行编译就可以为您进行静态分析提供神奇的力量。如果您要查询一个C代码库...想知道是否以某种方式使用了某些类型,学习如何使用type_traits可以为其建立目标工具。只需一点点C ++知识以及您已经拥有的编译器,您就可以为使用C的静态分析工具付出大笔的钱……
HostileFork说不要相信

1
我说的是Linux问题。(我刚刚注意到它说“ Linux Torvalds”哈!)
沮丧的

28

我认为这是不可能的,只要您不需要单独使用参数,就可以用双括号假装它。

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

21
尽管可以使用可变参数宏,但使用双括号是一个很好的建议。
大卫·罗德里格斯(DavidRodríguez)-德里贝斯

2
Microchip的XC编译器不支持可变参数宏,因此,最好的方法是使用双括号技巧。
gbmhunter '16

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;
}

如果编译器不了解可变参数宏,则还可以使用以下任一方法去除PRINT:

#define PRINT //

要么

#define PRINT if(0)print

第一个注释掉PRINT指令,第二个注释由于条件为NULL而阻止PRINT指令。如果设置了优化,则编译器应删除从未执行的指令,例如:if(0)print(“ hello world”); 或((void)0);


8
#define PRINT //不会用//替换PRINT
bitc

8
#define PRINT if(0)print也不是一个好主意,因为调用代码可能具有自己的else-if来调用PRINT。更好的是:#define PRINT if(true); else print
bitc

3
标准的“无所事事,优雅地做”是{} while(0)
vonbrand 2014年

if考虑代码结构的“请勿执行此操作” 的正确版本是:if (0) { your_code } else宏扩展终止之后的分号else。该while版本是这样的:while(0) { your_code } 随着这个问题do..while的版本是代码中do { your_code } while (0)被完成一次,保证。在所有三种情况下,如果your_code为空,则为适当do nothing gracefully
Jesse Chisholm

4

在这里为g ++进行了解释,尽管它是C99的一部分,所以应该适用于所有人

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

快速示例:

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

3
GCC的可变参数宏不是C99可变参数宏。GCC 具有 C99可变参数宏,但是G ++不支持它们,因为C99不属于C ++。
克里斯·卢茨

1
实际上,g ++会在C ++文件中编译C99宏。但是,如果使用“ -pedantic”进行编译,它将发出警告。
Alex B

2
不是C99。C99使用VA_ARGS宏)。
qrdl

1
C ++ 11还支持__VA_ARGS__,尽管早期版本的编译器也支持它们作为扩展。
Ethouris

1
这对于printf(“ hi”)无效;没有var args的地方。任何通用的方法来解决这个问题?
BTR Naidu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.