标准对宏扩展的重新扫描阶段(
#
/##
处理和参数替换之后)进行了如下规定(C17 草案,6.10.3.4 ¶2):
如果在替换列表扫描期间找到了要替换的宏的名称(不包括源文件的其余预处理标记),则不会替换它。此外,如果任何嵌套替换遇到被替换的宏的名称,则不会替换它。这些未替换的宏名称预处理标记不再可用于进一步替换,即使稍后在宏名称预处理标记本来会被替换的上下文中(重新)检查它们也是如此。
让我借此机会总结一下宏替换如何与
#
/##
交互 - 对象或类似函数的宏的处理和参数替换 M:
#
之后或与##
相邻的参数将被逐字替换(可能作为地标标记),然后由#
和##
处理。
#
和##
的相对评估顺序。这是否重要还不清楚。##
产生的所有地标标记均被删除。(此算法描述与标准中的算法描述不同,但至少对于本文的目的而言,它应该足够等效。)
令牌串联运算符
##
的应用如何与禁止递归宏扩展相互作用?具体来说,它如何影响某些递归扩展被阻止的区域的边界?
该网站上有一个类似的问题:Is a Repeated Macro Involution via token concatenation unspecified behavior?
注:
基于我下面分析的示例: 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, )
(预处理器可以按如下方式运行:
cpp
或gcc -E
(GCC),cl /E
(MSVC)。)
两个宏的计算结果如下
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)
:AC AC A + A AC A
) 的输出对应于情况 2(“无阻塞”)。具体来说,由令牌串联产生的两个 AC
实例的阻塞列表被重置,从而允许 AC
和 A
重新扩展。
CALL_RC(AC, A, )
:AC A + A A
) 的输出对应于情况 1(“阻塞”)。具体来说,由令牌串联 与地标产生的
A
的两个实例(即由空参数产生的与 ##
相邻的令牌)不会重置其阻止列表,从而防止进一步扩展。
诚然,对这两种情况进行区别对待是有道理的,但这实际上是符合标准的,还是标准对此含糊其辞?我特别指的是 C17 草案第 6.10.3.4 ¶2 节中的措辞(在本文最顶部引用)。
我不确定。 可以说,答案取决于令牌与地标连接后是否仍然是“相同”令牌(C17 草案,6.10.3.3 ¶3)。