【问题标题】:C++ Preprocessor Standard BehaviourC++ 预处理器标准行为
【发布时间】:2014-04-30 15:57:18
【问题描述】:

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

【问题讨论】:

  • 用 clang++ 运行我得到了eoe+su
  • vs2013 输出 eoe+eoe+su)
  • VC++2010 和宏调用 dds(xyz)pqr) 会发生什么?然后出现+et_l 材料中的任何一个吗?我在想您使用的其中一个词可能是 VC++2010 的#definedd,这可能导致“错误”扩展是正确的,尽管不是预期的。您也可以在调用dds 宏之前尝试#undef eoe#undef su,看看是否会改变输出。这是预处理器的(许多)问题之一。没有范围控制,几乎任何名称都可以由某个标头定义。

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


【解决方案1】:

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

来自C, A Reference Manual, 5th edition

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

注意展开式中的字词。这就是使您的示例无效的原因。现在,将其与以下内容结合起来: UPDATE:阅读下面的 cmets。

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

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

这可能很难考虑,但我相信您的示例应该在重新扫描期间识别出宏名称 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 节一致,重新扫描和进一步替换

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

【讨论】:

  • 此答案中没有任何内容支持您的说法,即一旦到达外部宏扩展的末尾,嵌套宏参数的确定就可以停止。即使您像您一样阅读“在扩展中”,它也会说“以便可以在扩展中识别宏的名称”,并且名称f .在dds(eoe) su) 中,dds(eoe) 首先扩展为f(eoe,,得到f(eoe, su)。然后,对宏名称的扫描从前一个展开结果的开头开始,在 f 处,两个宏参数都在该处可见。唯一正确的结果是 GCC 和 clang 给出的结果。
  • @hvd 是的,我正在校对我的答案并偶然发现了这一点。我想你是正确的。我现在正在编辑我的答案。
【解决方案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

【讨论】:

    【解决方案3】:

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

    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 。 无论出于何种原因,它都会这样做两次:)。

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

    【讨论】:

    • 我倾向于不同意您的论点:C++ 标准 16.3.4 重新扫描和进一步替换第 1 点“替换列表中的所有参数都被替换后,生成的预处理令牌序列将与所有后续预处理一起重新扫描源文件的标记,用于替换更多宏名称。”。所以“f(x”)应该被重新扫描并与它后面的“su)”结合起来。
    • 这就是预处理器错误所在的地方:f(x, 不会用 "su)" 重新扫描,它会用以 "et_l)" 结尾的其他东西重新扫描。在我的 VS 上,我有 "ript"" 而不是 y ,所以 VS 预处理器出错了。
    猜你喜欢
    • 2013-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-20
    • 2020-09-07
    • 2010-12-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多