【发布时间】:2021-08-23 03:30:53
【问题描述】:
我正在尝试实现与 C/C++ 兼容的宏处理。我可以正确处理许多极端情况,包括此处讨论的情况:Understanding the behavior of C's preprocessor when a macro indirectly expands itself。
但是,有一个极端情况,我从 gcc 和 clang 得到不同的答案,所以显然是错误的。代码类似于这里讨论的案例:Can we have recursive macros?
#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 ()()。我的逻辑是f NOEXPAND(F_AGAIN)()() 中的每个标记在其“隐藏集”中都有F,并且您永远无法从标记的隐藏集中删除元素。
在Can we have recursive macros? 中,最相关的答案是关于将宏绘制为蓝色而不是隐藏集,但我找不到任何关于其工作原理的完整描述。因此,我遵循Dave Prosser's 算法,该算法根据隐藏集解释了 cpp 行为。我认为这就是编译器在实践中所做的。
我的问题:我是在误解 Prosser 的算法,还是应该实施不同的算法?现代 C 预处理器的行为是否记录在任何地方?语言规范本身在这个问题上过于含糊。
【问题讨论】:
-
我将不得不更详细地查看这一点,但考虑到在处理
ID(F())时,首先替换ID参数中的宏。所以F()被处理了。一旦处理完成,它就结束了,完成,完成。以后的压制不记得了。这就像一个递归函数被调用并返回。它不再嵌套在任何东西中。然后ID(f F_AGAIN ()())被重新处理。 -
@πάνταῥεῖ:预处理器替换不做“简单、直接的文本替换”。首先,它对预处理器标记进行操作,而不是文本。其次,它有关于其操作顺序的规则,以及何时考虑替换令牌以及不考虑进一步替换。第三,它有运算符(
#和##)。我看到你错误地关闭了这个用户的上一个问题,因为它是重复的。请不要那样做。 -
@πάνταῥεῖ 众所周知,MSVC 多年来一直有一个错误的预处理器,所以它并不是那么简单。我所说的“窃听”是指像所讨论的那样的次要细节,但它破坏了复杂的宏。
-
@HolyBlackCat 这正是我开始的地方,但事实证明该标准在这一点上故意存在缺陷,因为他们担心影响现有编译器的合规性状态。如果标准指定了行为,我可能不会在这里询问它。不相信我?请参阅:open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#268(但请注意,我在该讨论中处理此案,这似乎与我对 Prosser 的阅读一致)。
-
“委员会的决定是“野外”的现实项目不会冒险进入这个领域”哈!
标签: c++ c c-preprocessor