我正在研究有关预处理器确切行为的 C++ 标准(我需要实现某种 C++ 预处理器)。 据我了解,我在下面编写的示例(以帮助我理解)应该是有效的:
#define dds(x) f(x,
#define f(a,b) a+b
dds(eoe)
su)
我希望第一个类似函数的宏调用
dds(eoe)
被 f(eoe,
替换(注意替换字符串中的逗号),然后在重新扫描输入时将其视为 f(eoe,su)
。
但是使用 Visual C++ 2010 进行的测试给了我这个(我告诉 VC++ 输出预处理的文件):
eoe+et_leoe+et_l
su)
这是违反直觉的,而且显然是不正确的。 这是 Visual C++ 2010 的错误还是我对 C++ 标准的误解? 特别是,像我一样在替换字符串的末尾添加逗号是不正确的吗? 我对 C++ 标准语法的理解是任何
preprocessing-token
都是允许的。
我没有 GCC 或其他版本的 Visual C++。我如何使用这些编译器进行验证?
我的答案对于 C 预处理器有效,但根据 Is a C++ preprocessor equal 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 节一致,重新扫描并进一步替换:
替换列表中的所有参数都被替换后#和## 处理完成后,所有地标预处理标记都会被删除。然后, 重新扫描生成的预处理令牌序列以及所有后续的 预处理源文件的标记,以替换更多宏名称。
据我所知,标准的
[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。
嗯,我看到的问题是预处理器执行以下操作
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 。 不管出于什么原因,它都会这样做两次:)。
您创建的这个场景是特定于编译器的,取决于预处理器如何选择处理定义扩展。