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

时间:2014-03-23 11:05:57

标签: c++ visual-c++ preprocessor c-preprocessor compiler-bug

我正在研究关于预处理器的确切行为的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的一个错误还是我对C ++标准的误解?特别是,像我一样在替换字符串的末尾添加逗号是不正确的吗?我对C ++标准语法的理解是允许任何preprocessing-token

编辑:

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

3 个答案:

答案 0 :(得分:8)

我的回答对C预处理器有效,但根据Is a C++ preprocessor identical to a C preprocessor?,差异与此案例无关。

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

  

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

注意扩展中的字样。这就是使你的例子无效的原因。现在,将其与此结合起来: 更新:阅读以下评论。

  

[...]通过写一个左括号,调用它来调用宏,   然后一次为每个形式参数的实际参数标记序列,   然后右括号。实际的参数标记序列是   以逗号分隔。

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

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

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

dds(eoe,su)

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

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

  

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

答案 1 :(得分:2)

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

我没能使它工作但是我设法为你找到一个丑陋的MSC解决方法,使用可变参数 - 你可能会发现它有用,或者你可能没有,但无论如何它是:

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

它扩展为:

(eoe+
su)

当然,这不能与 gcc clang 一起使用。

答案 2 :(得分:1)

嗯,我看到的问题是预处理器执行以下操作

ddx(x)变为f(x,

但是,f(x,也定义为f(a,b)),所以f(x,扩展为x +垃圾。

所以ddx(x)最终转换为x +垃圾(因为你定义了f(smthing)。

你的dds(eoe)实际上扩展为a + b,其中a是eoe而b是et_l。 无论出于何种原因它都会这样做两次:)。

您所做的这种情况是特定于编译器的,取决于预处理器选择如何处理定义扩展。