C++预处理程序标准行为

Jav*_*Man 15 c++ preprocessor visual-c++ compiler-bug c-preprocessor

我正在研究关于预处理器的确切行为的C++标准(我需要实现某种类型的C++预处理器).根据我的理解,我在下面编写的示例(以帮助我的理解)应该是有效的:

#define dds(x) f(x,
#define f(a,b) a+b
dds(eoe)
su)
Run Code Online (Sandbox Code Playgroud)

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

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

eoe+et_leoe+et_l
su)
Run Code Online (Sandbox Code Playgroud)

这是违反直觉的,显然是不正确的.它是VC++ 2010的一个错误还是我对C++标准的误解?特别是,像我一样在替换字符串的末尾添加逗号是不正确的吗?我对C++标准语法的理解是,任何preprocessing-token人都可以在那里使用.

编辑:

我没有GCC或其他版本的VC++.有人可以帮我验证这些编译器.

Fil*_*ves 8

我的答案对C预处理器有效,但根据C++预处理器是否与C预处理器相同?,这种差异与这种情况无关.

来自C,A参考手册,第5版:

当执行类似函数的宏调用时,在参数处理之后,通过正文的副本替换整个宏调用.参数处理如下进行.实际参数标记字符串与相应的形式参数名称相关联.然后创建正文的副本,其中每个出现的形式参数名称都被与其关联的实际参数标记序列的副本替换.然后,身体的副本将替换宏调用.[...]一旦扩展了宏调用,宏调用的扫描将在扩展开始时恢复,以便可以在扩展中识别宏的名称,以便进一步进行宏替换.

注意扩展中的单词.这就是使你的例子无效的原因.现在,将它与此结合起来: 更新:阅读下面的评论.

[...]宏通过写入其名称,左括号,然后是每个形式参数的实际参数标记序列,然后是右括号来调用.实际的参数标记序列用逗号分隔.

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

这可能很难想,但我相信你的例子应该发生的事情f是在重新扫描期间识别宏名称,并且由于后续的令牌处理显示了宏调用f(),你的例子是正确的并且应该输出你期望的.GCC和clang给出了正确的输出,根据这个推理,这也是有效的(并且产量等效输出):

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

dds(eoe,su)
Run Code Online (Sandbox Code Playgroud)

实际上,两个示例中的预处理输出都是相同的.至于你用VC++获得的输出,我会说你发现了一个bug.

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

在替换列表中的所有参数都已被替换并且#和##处理已经发生之后,将删除所有地标标记预处理标记.然后,重新扫描生成的预处理标记序列以及源文件的所有后续预处理标记,以替换更多的宏名称.


moc*_*ace 2

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

我没能成功,但我设法为你找到了一个丑陋的 MSC 解决方法,使用可变参数 - 你可能会发现它有帮助,也可能没有,但无论如何它是:

#define f(a,b) (a+b
#define dds(...) f(__VA_ARGS__)
Run Code Online (Sandbox Code Playgroud)

其展开为:

(eoe+
su)
Run Code Online (Sandbox Code Playgroud)

当然,这不适用于gccclang