【问题标题】:Problems with variadic macros in CC中可变参数宏的问题
【发布时间】:2011-06-02 05:15:11
【问题描述】:

我在 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));
}

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

【问题讨论】:

    标签: c c-preprocessor variadic-macros


    【解决方案1】:

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

    它现在可以正确编译和运行任意数量的参数和任意数量的嵌套,作为任何参数。谢谢大家!

    【讨论】:

      【解决方案2】:

      ## 运算符会进行精确的令牌替换,因此在这种情况下,它会尝试将令牌 "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 函数,而不是一个宏,因为编译器不知道宏,只有预处理器可以。

      【讨论】:

      • 它在没有标记粘贴的情况下正确解析(这实际上只是为了解决逗号问题),所以如果真的没有其他解决方案,我将使用两个单独的定义来确定是否存在是要传入的参数。
      【解决方案3】:

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

      只需稍加努力,您就可以构建宏,这些宏可能会对接收到的参数数量做出反应并进行正确的替换。 P99 为此提供了帮助:

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

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

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

      【讨论】:

      • 哇,P99 听起来很棒。 (我原本在这篇文章中有更多内容,但发现不允许换行,所以我会去编辑我原来的帖子......)
      猜你喜欢
      • 1970-01-01
      • 2012-04-04
      • 2011-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多