C语言中可变参数宏的问题

时间:2011-01-01 12:00:54

标签: c c-preprocessor variadic-macros

我在 C 的#define语句中遇到可选参数的问题,或者更具体地说是gcc 4.2:

bool func1(bool tmp) { return false; }
void func2(bool tmp, bool tmp2) {}
#define CALL(func, tmp, ...) func(tmp, ##__VA_ARGS__)

int main() {
   // this compiles
   CALL(func2, CALL(func1, false), false);

   // this fails with: Implicit declaration of function 'CALL'
   CALL(func2, false, CALL(func1, false));
}

这显然是一个人为的例子,但确实显示了问题。有谁知道如何才能正确地“解析”可选参数?


附加信息: 如果我在##之前移除__VA_ARGS__,并执行以下操作:

bool func2(bool tmp, bool tmp2) { return false; }
#define CALL(func, tmp, ...) func(tmp, __VA_ARGS__)

int main() {
   CALL(func2, false, CALL(func2, false, false));
}

编译,但它不再适用于零参数,因为它将解析为func(tmp, )

编辑:在将我的所有代码转换为依赖P99而不是之前的代码(最终破坏了我的代码,哎呀)之后,我意外地发现这有效:

bool func1(bool tmp) { return false; }
void func2(bool tmp, bool tmp2) {}
#define CALL2(func, tmp, p...) func(tmp, ##p)
#define CALL(func, tmp...) CALL2(func, tmp)

int main() {
   // works
   CALL(func2, CALL(func1, false), false);

   // ...also works
   CALL(func2, false, CALL(func1, false));
}

编译并使用任意数量的参数(并传入和返回正确的值),但......这应该是合法的吗?

3 个答案:

答案 0 :(得分:14)

##运算符执行精确的标记替换,因此在这种情况下,它尝试将标记"CALL(func1, false)"作为func1 C 函数的最后一个参数发送

问题在于CALL是一个宏,而无法##__VA_ARGS__列表中嵌套可变参数宏调用。

当内部宏作为命名参数传递时它工作的原因是因为预处理器将解析内部宏的命名参数,而不是##__VA_ARGS__列表,其中只有简单的令牌替换。

解决此问题的一种方法是将内部CALL的结果分配给占位符变量,然后将其传递给宏。

int main() {
   CALL(func2, CALL(func1, false), false);

   bool result = CALL(func1, false);
   CALL(func2, false, result);
}

另一种解决方法是使用__VA_ARGS__作为func函数的唯一参数,这样就可以传入嵌套的宏调用,如下所示:

#define CALL(func, ...) func(__VA_ARGS__)

int main() {
   CALL(func2, false, CALL(func2, false, false));
}

让我们更详细地分析你的困境:

CALL(func2, false, CALL(func1, false))

在此特定宏调用中,CALL现在为("func2", "tmp", CALL(func1, false)) 因此,它会尝试调用func1,传入tmp,以及CALL(func1, false)

这是在预处理器和实际C编译器之间绘制线的地方。

预处理器,一旦开始进行替换,它就完成了解析,因此编译器接收CALL(func1, false)作为实际的C函数,而不是宏,因为编译器不知道关于宏,只有预处理器。

答案 1 :(得分:4)

您正在使用带有, ##构造的gcc扩展名。如果您考虑到可移植性,那么使用它可能不是一个好主意。

通过一点点努力,您可以构建宏,这些宏可能会对它们收到的参数数量做出反应并做出正确的替换。 P99为此提供了帮助:

#define CALL(...) P99_IF_EQ_2(P99_NARG(__VA_ARGS__))(dosomethingwithtwo(__VA_ARGS__))(dosomethingwithmore(__VA_ARGS__))

但在你的情况下,我认为有一个简单的解决方案:

#define CALL(func, ...) func(__VA_ARGS__)

答案 2 :(得分:0)

所以我只是将我的代码转换为使用原始帖子末尾提到的设计,它突然又开始工作了。我将假设我的P99包装纸上有一个拼写错误。

它现在可以为任意数量的参数和任何数量的嵌套编译和运行,就像任何参数一样。谢谢大家!