MSVC不能正确扩展__VA_ARGS__。

71

考虑以下代码:

#define F(x, ...) X = x and VA_ARGS = __VA_ARGS__
#define G(...) F(__VA_ARGS__)
F(1, 2, 3)
G(1, 2, 3)

期望输出对于两个宏都是X = 1 and VA_ARGS = 2, 3,我使用GCC得到了这个结果,然而,MSVC将其展开为:

X = 1 and VA_ARGS = 2, 3
X = 1, 2, 3 and VA_ARGS =

也就是说,__VA_ARGS__被扩展为一个单独的参数,而不是分解为多个参数。

有什么解决方法吗?

3个回答

73

编辑: 最近的MSVC可能通过使用/Zc:preprocessor/experimental:preprocessor选项来解决此问题。 有关详细信息,请参见此处

MSVC的预处理器似乎与标准规范行为非常不同。
可能以下解决方法会有所帮助:

#define EXPAND( x ) x
#define F(x, ...) X = x and VA_ARGS = __VA_ARGS__
#define G(...) EXPAND( F(__VA_ARGS__) )

1
@bk1e:很抱歉,由于我没有能力,在这里无法详细解释即将到来的C++标准中的预处理过程,但它与C99的差异不大。 - Ise Wisteria
2
有人能解释一下吗?"and VA_ARGS = VA_ARGS"是有效的C代码还是只是作为注释的可读文本?如果这是有效的代码,那么"and VA_ARGS = VA_ARGS"是做什么的?谢谢。 - Virus721
@q126y:抱歉,我无法解释技术原因。我之前在Microsoft Connect的页面上看到过这个问题的报告,但现在该页面似乎无法访问。我认为这种行为是MSVC的一个bug,而答案中的“EXPAND”宏只是一种纯粹的解决方法。 - Ise Wisteria
3
我理解了......为了简单起见,假设__VA_ARGS__1, 2EXPAND(x) xF(__VA_ARGS__)替换为字面上的F(1, 2),这样F(1, 2)会被处理,而不是处理F(__VA_ARGS__),因为__VA_ARGS__被视为一个单一实体。 - solstice333
2
@IseWisteria,@rbrich已经在下面提到了现在有编译器开关/Zc:preprocessor,请参见此处,它将正确展开__VA_ARGS__。也许值得编辑您的答案,以包括这个解决方案和您原来的解决方案? - hassec
显示剩余4条评论

32

以下是我发布在Microsoft支持问题上的内容:

下面的程序会因为预编译器错误地展开__VA_ARGS__而导致编译错误:

#include <stdio.h>

#define A2(a1, a2) ((a1)+(a2))

#define A_VA(...) A2(__VA_ARGS__)

int main(int argc, char *argv[])
{
    printf("%d\n", A_VA(1, 2));
    return 0;
}
从预处理器的角度来看,printf被展开成:
      printf("%d\n", ((1, 2)+()));

而不是
      printf("%d\n", ((1)+(2)));

我向Microsoft编译器团队的开发人员询问了这个问题,但得到的回答并不令人满意:

“你好:在这种情况下,Visual C++编译器的行为是正确的。如果将初始宏调用中与'...'匹配的标记组合成单个实体的规则(16.3/p12)与子宏在参数替换之前扩展的规则相结合(16.3.1/p1),那么在这种情况下,编译器会认为A2只有一个参数:因此会出现错误消息。”

6
感谢您传达微软公司的解释。他们似乎将16.3.1/p12中的“合并为单个项目”解释为“合并为单个、永久不可分割的预处理器标记”,这似乎不太有用。我希望替换的标记至少在16.3.4中所示的重新扫描步骤中重新分开,这似乎是其他编译器正在做的事情。 - jcl
我非常同意,但我显然被GCC和Clang宠坏了。你们两个能否想到MSVC行为的用例,还是说这只是一种为了一致性而存在的事情,尽管表达能力受到影响?我已经超出了我的领域,但是“...在这种情况下,编译器相信...”听起来并不令人信服,更不用说对于任何试图编写不可知代码的人有用了。我有一些解决方法的想法,但我的Windows分区已经失效了。无论如何,我希望看到有人尝试它。抱歉发牢骚和/或回复旧帖。 - John P
3
顺便说一下,这里有另一个关于同样问题的 bug(链接见上),团队承认这是一个错误,但表示优先级不够高,不打算修复(7 年前)。 - BeeOnRope
4
现在有一个 /experimental:preprocessor/Zc:preprocessor 编译器开关,可以使预处理器的行为正确无误。这对我来说解决了问题。 - rbrich

0

您使用的是哪个版本的MSVC?您需要Visual C++ 2010。

__VA_ARGS__最初由C99引入。MSVC从未尝试支持C99,因此也没有添加支持。

然而,现在__VA_ARGS__已经包含在新的C++标准C++2011(以前称为C++0x)中,微软显然计划支持它,因此它已经在最近的MSVC版本中得到了支持。

顺便说一下,您需要将源文件的后缀名更改为.cpp才能获得此支持。MSVC很长时间没有更新其C前端了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接