15

我正在研究预处理器的确切行为的 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++。有人可以帮我验证这些编译器。

4

3 回答 3

8

我的回答对 C 预处理器有效,但根据Is a C++ preprocessor same to a C preprocessor?,差异与这种情况无关。

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

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

注意扩展内的单词。这就是使您的示例无效的原因。现在,把它和这个结合起来: 更新:阅读下面的评论。

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

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

这可能很难考虑,但我相信您的示例应该发生的情况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 节“重新扫描和进一步替换”一致:

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

于 2014-03-23T11:46:47.890 回答
2

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

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

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

它扩展为:

(eoe+
su)

当然,这不适用于gccclang

于 2014-03-23T12:16:48.180 回答
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 。无论出于何种原因,它都会这样做两次:)。

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

于 2014-03-23T11:44:06.610 回答