visual c++预处理器标准行为

visual C++ Preprocessor Standard Behaviour

本文关键字:标准 处理器 c++ 预处理 visual      更新时间:2023-10-16

我正在研究关于预处理器确切行为的c++标准(我需要实现某种c++预处理器)。根据我的理解,下面我举的例子(帮助我理解)应该是有效的:

#define dds(x) f(x,
#define f(a,b) a+b
dds(eoe)
su)

我希望像宏调用dds(eoe)这样的第一个函数被f(eoe,替换(注意替换字符串中的逗号),然后在重新扫描输入时将其视为f(eoe,su)

但是vc++ 2010的测试给了我这个(我告诉vc++输出预处理文件):

eoe+et_leoe+et_l
su)

这是反直觉的,显然是不正确的。是vc++ 2010的bug还是我对c++标准的误解?特别是,像我这样在替换字符串的末尾放一个逗号是不正确的吗?我对c++标准语法的理解是任何preprocessing-token都是允许的。

编辑:

我没有GCC或其他版本的vc++。谁能帮我验证一下这些编译器

我的答案对C预处理器是有效的,但是根据c++预处理器与C预处理器相同吗?

选自C, A Reference Manual, 5th edition:

当遇到类函数的宏调用时,整个宏调用被调用在参数处理之后,由主体的副本替换。参数处理步骤如下。实际参数令牌字符串是与相应的形式参数名相关联。的副本然后构造主体,其中每次出现一个形式参数Name被替换为实际参数标记序列的副本与之相关。然后,主体的这个副本替换宏呼叫[…一旦宏调用被扩展,将扫描宏调用在展开的开始处恢复,以便宏的名称可以在展开符中识别,以便进一步使用宏更换。

注意展开中有两个字。这就是为什么你的例子无效。 UPDATE:阅读下面的注释。

[…调用宏的方式是写它的名字,左括号,然后每个形式参数的实际参数符号序列,然后是右括号。实际的参数标记序列为以逗号分隔。

基本上,这一切都归结为预处理器是否只在前一个扩展中重新扫描进一步的宏调用,或者它是否继续读取扩展后出现的令牌。

这可能很难想象,但我相信在您的示例中应该发生的情况是,宏名称f在重新扫描期间被识别出来,并且由于随后的令牌处理显示了f()的宏调用,因此您的示例是正确的,应该输出您所期望的内容。GCC和clang给出正确的输出,根据这个推理,这也是有效的(并产生等效的输出):

#define dds f
#define f(a,b) a+b
dds(eoe,su)

的确,两个例子中的预处理输出是相同的。至于你用vc++得到的输出,我会说你发现了一个bug。

这与C99第6.10.3.4节以及c++标准第16.3.4节一致,重新扫描和进一步替换:

替换列表中所有参数替换完成后,#和##处理已经完成,所有位置标记预处理令牌都被删除。然后,重新扫描产生的预处理令牌序列,以及所有后续的预处理源文件的标记,以便替换更多的宏名称。

据我所知,标准的[cpp.subst/rescan]部分中没有任何内容使您所做的事情非法,并且clanggcc将其扩展为eoe+su是正确的,并且MSC (Visual c++)行为必须报告为错误。

我没能让它工作,但我设法为你找到一个丑陋的MSC解决方案,使用变量-你可能会发现它有帮助,或者你可能没有,但在任何情况下,它是:

#define f(a,b) (a+b
#define dds(...) f(__VA_ARGS__)

展开为:

(eoe+
su)
当然,这在gccclang下是行不通的。

嗯,我看到的问题是,预处理器做以下

ddx(x)变成f(x,

然而,f(x)也被定义了(即使它被定义为f(a,b)),所以f(x)展开为x+垃圾。

所以ddx(x)最终转换成x +垃圾(因为你定义了f(某个东西,)。

你的dds(eoe)实际上展开成a+b,其中a是eoe, b是et_l。不管出于什么原因,它会重复两次:)。

这个场景是编译器特定的,取决于预处理器选择如何处理定义展开