令牌串联运算符##的应用如何与禁止递归宏扩展相互作用?

问题描述 投票:0回答:1

标准规则

标准对宏扩展的重新扫描阶段(

#
/
##
处理和参数替换之后)进行了如下规定(C17 草案,6.10.3.4 ¶2):

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

让我借此机会总结一下宏替换如何与

#
/
##
交互 - 对象或类似函数的宏的处理和参数替换 M:

  1. #
    之后或与
    ##
    相邻的参数将被逐字替换(可能作为地标标记),然后由
    #
    ##
    处理。
    • 请注意,标准中并未规定
      #
      ##
      的相对评估顺序。这是否重要还不清楚
  2. 对于每个其他参数,首先将其对应的参数完全宏扩展,然后用结果替换该参数。
    • 这些参数的宏扩展就像它们单独存在一样,即不考虑替换列表中的参数后面括号中的函数参数,这与作为参数参数的一部分提供的函数参数不同。
  3. ##
    产生的所有地标标记均被删除。
  4. 重新扫描生成的标记序列 S 以及源文件的所有后续预处理标记,以便评估更多宏。
    • 在此过程中,即使在另一个上下文中进行检查,S中进一步出现的MS的扩展也不会扩展。

(此算法描述与标准中的算法描述不同,但至少对于本文的目的而言,它应该足够等效。)

问题

令牌串联运算符

##
的应用如何与禁止递归宏扩展相互作用?具体来说,它如何影响某些递归扩展被阻止的区域的边界?


该网站上有一个类似的问题:Is a Repeated Macro Involution via token concatenation unspecified behavior?
注:

  • 它的例子很复杂。
  • 它的答案没有涉及任何细节。
  • 我的问题试图显示详细的逐步评估。
  • 我的问题显示了两个宏扩展,它们应该(可以想象)被相同地对待,但事实并非如此。
c language-lawyer c-preprocessor token-pasting-operator
1个回答
0
投票

基于我下面分析的示例: 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)

GCC 和 MSVC (
AC AC A + A AC A
) 的输出对应于情况 2(“无阻塞”)。具体来说,由令牌串联产生的两个
AC
实例的阻塞列表被重置,从而允许
AC
A
重新扩展。

CALL_RC(AC, A, )

GCC 和 MSVC (
AC A + A A
) 的输出对应于情况 1(“阻塞”)。具体来说,由令牌串联
 与地标 
产生的 A 的两个实例(即由空参数产生的与
##
相邻的令牌)不会重置其阻止列表,从而防止进一步扩展。

明确性

诚然,对这两种情况进行区别对待是有道理的,但这实际上是符合标准的,还是标准对此含糊其辞?我特别指的是 C17 草案第 6.10.3.4 ¶2 节中的措辞(在本文最顶部引用)。

我不确定。 可以说,答案取决于令牌与地标连接后是否仍然是“相同”令牌(C17 草案,6.10.3.3 ¶3)。

© www.soinside.com 2019 - 2024. All rights reserved.