我正在尝试实现兼容C/C++的宏处理。我可以正确地处理许多角落的情况,包括这里讨论的情况:理解C';当宏间接扩展自身时,s预处理器。
然而,在一个角落里,我得到了与gcc和clang不同的答案,所以我显然错了。代码与这里讨论的情况类似:我们可以使用递归宏吗?
#define ID(arg) arg
#define EMPTY
#define NOEXPAND(macro) macro EMPTY
#define F_AGAIN() F
#define F() f NOEXPAND(F_AGAIN)()()
ID(F())
我得到f F ()
,但是gcc/clang输出f f F_AGAIN ()()
。我的逻辑是CCD_ 3中的每个令牌在其"0"中都有CCD_;隐藏集";,并且您永远不能从令牌的隐藏集中移除元素。
在"我们能拥有递归宏吗?"中?,最相关的答案是将宏绘制为蓝色,而不是隐藏集,但我找不到任何完整的描述。因此,我遵循Dave Prosser的算法,该算法用隐藏集来解释cpp行为。我认为这就是编译器在实践中所做的。
我的问题是:我是误解了Prosser的算法,还是应该实现一个不同的算法?现代C预处理器的行为有记录吗?语言规范本身在这个问题上过于模糊。
好吧,我想我已经弄清楚发生了什么。编译器不实现Prosser的算法,句号。尽管Prosser的算法确实解决了语言规范中的特定已知缺陷,但它并没有解释实际编译器扩展宏的方式。
与隐藏集不同,似乎有两种禁用位在发挥作用:每宏禁用位和每令牌禁用位。
每当扩展M
时,在重新扫描阶段设置宏M
的每个宏禁用位,并且一旦完成M
的扩展就清除该禁用位。
如果在禁用M
的任何时候,预处理器处理M
的实例(无论在语法上是否对扩展有效(,则它将该特定的M
令牌标记为禁用。编译器不仅在禁用令牌时避免扩展令牌,而且在任何上下文中,甚至在M
的扩展完成后,也会永久禁用对令牌进行扩展的任何考虑。
让我们来看一些更简单的例子:
#define ID(arg) arg
#define LPAREN (
#define F_AGAIN() F
#define F() f F_AGAIN LPAREN)()
F() // => f F_AGAIN ()()
ID(F()) // => f f F_AGAIN ()()
ID(ID(F())) // => f f f F_AGAIN ()()
#define G() g G LPAREN)()
G() // => g G ()()
ID(G()) // => g G ()()
ID(ID(G())) // => g G ()()
首先考虑一下G()
的扩展过程中发生了什么。它扩展到令牌列表g G LPAREN)()
,并且在重新扫描期间,该令牌列表中的G
被永久禁用。现在,无论您通过ID
重新扫描令牌列表多少次,G
都永远无法扩展。
接下来考虑一下F()
发生了什么。它扩展到令牌列表f F_AGAIN LPAREN)()
。在重新扫描过程中,它会扩展到f F_AGAIN ()()
。由于F_AGAIN
当前不是禁用的宏,因此这些输出令牌都不会被禁用。因此,现在在对ID
宏的任何重新扫描中,F_AGAIN
都将被扩展一次,也会导致F
被扩展一一次。
在这种情况下,实际上可以理解语言规范:
如果在扫描替换列表(不包括源文件的其余预处理标记(的过程中找到了要替换的宏的名称,则不会对其进行替换。此外,如果任何嵌套替换遇到要替换的宏的名称,则不会进行替换。这些未被替换的宏名称预处理令牌不再可用于进一步替换,即使它们稍后在宏名称预预处理令牌本来会被替换的上下文中进行(重新(检查也是如此。
我想打乱我直觉的部分是"它不被替换";听起来很无害——尤其是在宏无论如何都不会被替换的情况下,例如,因为它是一个类似函数的宏,后面没有一个开放的paren(
。然后";代币不再可用于进一步替换";让它听起来像是在描述前一句话的结果,而规范的真正含义是,";编译器会主动破坏该令牌,并禁止其再次扩展">