我正在研究预处理器的确切行为的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 回答
据我所知,标准的
[cpp.subst/rescan]
部分中没有任何内容可以使您的行为非法,并且clang和gcc正确地将其扩展为eoe+su
,并且MSC(Visual C)行为必须报告为错误 .我没能使它工作,但我设法为你找到一个丑陋的MSC解决方案,使用可变参数 - 你可能会发现它有用,或者你可能没有,但无论如何它是:
它扩展为:
当然,这不适用于gcc和clang .
我的答案对C预处理器有效,但根据Is a C++ preprocessor identical to a C preprocessor?,差异与此情况无关 .
来自C,A参考手册,第5版:
注意扩展中的单词 . 这就是使你的例子无效的原因 . 现在,结合它: UPDATE :阅读下面的评论 .
基本上,这一切都归结为预处理器是否仅在之前的扩展中重新扫描进一步的宏调用,或者它是否会继续读取即使在扩展后出现的令牌 .
这可能很难想,但我相信你的例子应该发生的事情是在重新扫描期间识别宏名称
f
,并且由于后续的令牌处理显示f()
的宏调用,你的例子是正确的,应该输出你的期望 . GCC和clang给出了正确的输出,根据这个推理,这也是有效的(并且产量等效输出):实际上,两个示例中的预处理输出都是相同的 . 至于你用VC获得的输出,我会说你发现了一个bug .
这符合C99第6.10.3.4节以及C标准第16.3.4节,重新扫描和进一步更换:
好吧,我看到的问题是预处理器执行以下操作
ddx(x)变为f(x,
但是,f(x,也被定义为(即使它被定义为f(a,b)),所以f(x,扩展为x garbage .
所以ddx(x)最终转换为x garbage(因为你定义了f(smthing)) .
你的dds(eoe)实际上扩展为b,其中a是eoe,b是et_l . 无论出于何种原因,它都会这样做两次:) .
您所做的这种情况是特定于编译器的,取决于预处理器选择如何处理定义扩展 .