【问题标题】:Nested invocations of function-like macros类函数宏的嵌套调用
【发布时间】:2021-11-25 04:34:50
【问题描述】:

考虑以下代码sn-p:

#define FOO() BAR
#define BAR() FOO

FOO()()()

C 标准告诉我们,在参数替换等之后,由宏调用产生的预处理标记被重新扫描以获取更多宏名称,忽略生成它们的宏的名称(c99,6.10.3.4p1- 2)

因此,我希望预处理器将 sn-p 转换为 BAR()(),然后是 FOO(),然后停止,因为令牌 FOO 是宏 FOO 的结果,并且不是t 被识别为宏名称。

但是 GCC 和 clang 都给了我结果BAR,表明它实际上是在扩大一次。仅当宏的调用“发生”在参数列表(其中不再忽略宏名称FOO)而不是宏名称本身时,这才有意义。这是非常不直观的,我发现标准中没有提到它。我错过了什么?

提前致谢!

【问题讨论】:

标签: c macros language-lawyer c-preprocessor c99


【解决方案1】:

这是来自 C 标准的相关段落:

6.10.3.4 重新扫描和进一步替换

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

2 如果在替换列表的扫描期间找到被替换的宏的名称(不包括源文件的其余预处理标记),则不会替换它。此外,如果任何嵌套替换遇到被替换的宏的名称,它不会被替换。这些未替换的宏名称预处理标记不再可用于进一步替换,即使它们稍后在本应替换宏名称预处理标记的上下文中(重新)检查。

3 生成的完全被宏替换的预处理标记序列即使类似于一个预处理指令也不会被处理,但其中的所有 pragma 一元运算符表达式随后会按照下面 6.10.9 中的规定进行处理。

例如,如果你写过

#define QQ() QQ
QQ()()()

扩展将只是QQ()(),因为根据2),当在替换列表的扫描过程中找到QQ时,它不会被扩展。

相反,在您的示例中,FOO() 的替换列表中未找到 FOOBAR 后跟 (),这导致它被扩展,而 BAR 在替换中未找到BAR() 的列表,但 FOO 后跟最后一组 () 再次展开。

短语如果任何嵌套替换遇到被替换的宏的名称,则不会被替换是指在宏参数扩展期间发生的替换。在您的示例中,替换是迭代发生的,而不是递归发生的,因此额外的 () 集将导致进一步扩展。

【讨论】:

  • 哦,很好。现在我不必写同样的东西了。
【解决方案2】:

C 预处理器实现了Prosser's blue paint algorithm

在函数符号展开的那一刻,该符号为painted blue,蓝色符号不再展开。

要完全了解 CPP 的工作原理,您必须在 Google 上搜索“蓝色油漆”并阅读...

【讨论】:

  • 感谢这个兔子洞。我跟着它,找到了《新 C 标准 - 经济和文化评论》一书,该书声称该标准未定义我的问题的答案(book,第 1970 页,“但是,鉴于定义...... ")。
  • @koorkevani 了解 CPP 工作原理的最佳方法是用您喜欢的语言自己实现它。我用 lisp 实现了它,它需要几百行代码。这在概念上并不困难,您可以使用 IOCCC 获奖代码作为输入来测试您的实现。
猜你喜欢
  • 2019-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-17
相关资源
最近更新 更多