提问人:Lover of Structure 提问时间:10/10/2023 最后编辑:Lover of Structure 更新时间:10/10/2023 访问量:67
令牌连接运算符 ## 的应用如何与禁止递归宏扩展交互?
How does application of the token concatenation operator ## interact with the prohibition against recursive macro expansion?
问:
标准和问题的规则
该标准对宏扩展的重新扫描阶段(在 /-处理和参数替换之后)进行了如下说明(C17 草案,6.10.3.4 ¶2):#
##
如果在扫描替换列表期间找到要替换的宏的名称(不包括源文件的其余预处理标记),则不会替换该宏。此外,如果任何嵌套替换遇到要替换的宏的名称,则不会替换该宏。这些未替换的宏名称预处理标记不再可用于进一步替换,即使稍后在该宏名称预处理令牌将被替换的上下文中对其进行(重新)检查也是如此。
让我借此机会总结一下宏替换如何与类似对象或函数的宏 M 的 /-processing 和参数替换进行交互:#
##
- 后面或相邻的参数将被逐字替换(可能作为地标标记),然后由 和 处理。
#
##
#
##
- 请注意,标准未指定 和 的相对评估顺序。这是否重要尚不清楚。
#
##
- 请注意,标准未指定 和 的相对评估顺序。这是否重要尚不清楚。
- 对于每个其他参数,首先将其对应的参数完全宏展开,然后由结果替换该参数。
- 这些参数的宏扩展就好像它们孤立存在一样,即不考虑替换列表中的参数之后括号中的函数参数,这与作为参数参数的一部分提供的函数参数不同。
- 将删除所有由此产生的地标标记。
##
- 将重新扫描生成的标记序列 S 以及源文件的所有后续预处理标记,以便评估更多宏。
- 在此过程中,即使在另一个上下文中检查,也不会在 S 中进一步出现 M 或 S 的扩展。
(此算法描述与标准中的描述并不完全相同,但至少就本文而言,它应该足够等效。
现在的问题是:令牌连接运算符 ##
的应用如何与禁止递归宏扩展相互作用?具体来说,它如何影响某些递归扩展被阻塞的区域的边界?GCC 和 MSVC 似乎对带有地标标记的串联与普通串联的处理方式不同。
例
让我们考虑以下示例:
#define RECURSIONTEST(a, b, c) a ## c + b ## c
#define AC A
#define A AC A
#define CALL_RC(x, y, z) RECURSIONTEST(x, y, z)
CALL_RC(AC, A, C)
CALL_RC(AC, A, )
(预处理器可以按如下方式运行:或 (GCC)、(MSVC)。cpp
gcc -E
cl /E
这两个宏的计算结果如下
CALL_RC(AC, A, C) -> AC AC A + A AC A
CALL_RC(AC, A, ) -> AC A + A A
同时使用 GCC 和 MSVC。
让我们手动计算应该发生什么:CALL_RC(AC, A, C)
CALL_RC(AC, A, C)
[def of CALL_RC(x, y, z): RECURSIONTEST(x, y, z)]
arg x: AC -> A /*blk:AC*/ -> AC A /*blk:AC,A*/
arg y: A -> AC A /*blk:A*/ -> A A /*blk:A,AC*/
arg z: C
after arg subst:
RECURSIONTEST(AC A /*blk:AC,A*/, A A /*blk:A,AC*/, C)
// rescan for add'l macros:
RECURSIONTEST(AC A /*blk:AC,A*/, A A /*blk:A,AC*/, C) /*blk: CALL_RC*/
[def of RECURSIONTEST(a, b, c): a ## c + b ## c]
##-arg a: AC A /*blk:AC,A*/
##-arg b: A A /*blk:A,AC*/
##-arg c: C
concat 1: AC AC
// blocked for the left-hand AC: AC, A
// Are A and AC blocked for the right-hand AC?
concat 2: A AC
// blocked for A: A, AC
// Are A and AC blocked only for A or also for AC?
after ##:
AC AC + A AC
// rescan for add'l macros:
AC AC + A AC
// blocked globally: CALL_RC, RECURSIONTEST
// blocked for the 1st AC: AC, A
// perhaps (*) blocked locally for the 2nd and 3rd AC: A, AC
// blocked for A: A, AC
after macro expansion:
case 1 (blocking for (*)):
result: AC AC + A AC /*blk:AC,A*/
case 2 (no blocking for (*)):
2nd/3rd AC: AC -> A /*blk:AC*/ -> AC A /*blk:AC,A*/
result: AC AC A + A AC A /*blk:A,AC*/
// no further expansion possible in either case
在这里,“blk”表示前面表达式中的“为递归扩展而阻止的宏”。特别指出了局部块(用于子表达式)。
现在让我们对以下方面进行相同的计算:CALL_RC(AC, A, )
CALL_RC(AC, A, )
[def of CALL_RC(x, y, z): RECURSIONTEST(x, y, z)]
arg x: AC -> A /*blk:AC*/ -> AC A /*blk:AC,A*/
arg y: A -> AC A /*blk:A*/ -> A A /*blk:A,AC*/
arg z: <empty>
after arg subst:
RECURSIONTEST(AC A /*blk:AC,A*/, A A /*blk:A,AC*/, )
// rescan for add'l macros:
RECURSIONTEST(AC A /*blk:AC,A*/, A A /*blk:A,AC*/, ) /*blk: CALL_RC*/
[def of RECURSIONTEST(a, b, c): a ## c + b ## c]
##-arg a: AC A /*blk:AC,A*/
##-arg b: A A /*blk:A,AC*/
##-arg c: <placemarker>
concat 1: AC A
// blocked for AC: AC, A
// Are A and AC blocked for A?
concat 2: A A
// blocked for the left-hand A: A, AC
// Are A and AC blocked for the right-hand A?
after ##:
AC A + A A
// rescan for add'l macros:
AC A + A A
// blocked globally: CALL_RC, RECURSIONTEST
// blocked for AC: AC, A
// perhaps (**) blocked locally for the 1st and 3rd A: A, AC
// blocked for the 2nd A: A, AC
after macro expansion:
case 1 (blocking for (**)):
result: AC A + A A /*blk:AC,A*/
case 2 (no blocking for (**)):
1st/3rd A: A -> AC A /*blk:A*/ -> A A /*blk:A,AC*/
result: AC A A + A A A /*blk:A,AC*/
// no further expansion possible in either case
分析
CALL_RC(AC, A, C)
:
GCC 和 MSVC () 的输出对应于情况 2(“无阻塞”)。具体而言,令牌串联产生的两个实例的阻止列表将重置,从而允许并重新展开。AC AC A + A AC A
AC
AC
A
CALL_RC(AC, A, )
:
GCC 和 MSVC () 的输出对应于情况 1(“阻塞”)。具体而言,由令牌与地标连接生成的两个实例(即与空参数生成的令牌相邻)不会重置其阻止列表,从而阻止进一步扩展。AC A + A A
A
##
诚然,对这两种情况进行区别对待是有道理的,但这实际上是从标准中得出的,还是标准对此含糊不清?我特别指的是 C17 草案的 6.10.3.4 ¶2 中的措辞(在本文的最顶部引用)。
可以说,答案取决于令牌在与地标连接后是否仍然是“相同”的令牌(C17 草案,6.10.3.3 ¶3)。
这个网站上有一个类似的问题:通过令牌串联重复的宏调用是否是未指定的行为?
注意:
- 它的例子令人费解。
- 它的答案没有详细说明。
- 我的问题试图显示详细的分步评估。
- 我的问题显示了两个宏观扩展,它们应该(可以想象)被同等对待,但事实并非如此。
答: 暂无答案
评论
/Zc:preprocessor
,它“启用符合 C99 和 C++ 及更高版本标准的基于令牌的预处理器”。请考虑使用 .另见 1 和 2。/Zc:preprocessor
/Zc:preprocessor