为什么某些预处理器宏不展开,除非它们是另一个宏的参数?

Why are some preprocessor macros not expanded unless they are arguments to another macro?

在某些情况下,某些标记序列未完全预处理。 例如:

#define EMPTY()
#define DELAY(x) x EMPTY()
#define PRAGMA(args) _Pragma(args)

#define WRAP( BODY ) { BODY }

#define LOOP_Good( body, i, LB, UB ) \
WRAP( \
    DELAY(PRAGMA)("omp parallel for") \
    for( i = LB; i < UB; ++i ){ \
        body \
    } \
)

#define LOOP_Bad( body, i, LB, UB ) \
{ \
    DELAY(PRAGMA)("omp parallel for") \
    for( i = LB; i < UB; ++i ){ \
        body \
    } \
}

#define LOOP_Good_Again( body, i, LB, UB ) \
{ \
    PRAGMA("omp parallel for") \
    for( i = LB; i < UB; ++i ){ \
        body \
    } \
}

// Good
int i;
int lower_i = 0;
int upper_i = 10;

LOOP_Good( printf("%d\n", i);, i, lower_i, upper_i )

// Bad
LOOP_Bad( printf("%d\n", i);, i, lower_i, upper_i )

// Good again
LOOP_Good_Again( printf("%d\n", i);, i, lower_i, upper_i )

其中(使用 -E -fopenmp gcc 9.1)扩展到以下内容(带格式):

int i;
int lower_i = 0;
int upper_i = 10;

// Good
{ 
  #pragma omp parallel for
  for( i = lower_i; i < upper_i; ++i ){ 
    printf("%d\n", i); 
  } 
}

// Bad
{ 
  PRAGMA ("omp parallel for") 
  for( i = lower_i; i < upper_i; ++i ){ 
    printf("%d\n", i); 
  } 
}

// Good again
{ 
  #pragma omp parallel for
  for( i = lower_i; i < upper_i; ++i ){ 
    printf("%d\n", i); 
  } 
}

在 'good' 的情况下,DELAY(PRAGMA) 扩展为 PRAGMA,然后扩展(使用相邻参数)为 _Pragma(...)

在 'bad' 的情况下,DELAY(PRAGMA) 扩展为 PRAGMA 但处理停止并且 PRAGMA 留在输出中。 如果您获取 'bad' 输出并重新处理它(使用所有先前定义的宏),它会正确扩展。

唯一的区别是 'good' 情况,DELAY(PRAGMA)WRAP 宏参数的一部分,而 'bad' 情况不通过 DELAY(PRAGMA) 到任何宏。如果在 'bad' 的情况下,我们单独使用 PRAGMA,问题就解决了(如 'good again' 的情况)。

'good' 和 'bad' 案例中不同行为的原因是什么?

在糟糕的情况下,您打算作为 PRAGMA 的参数的内容永远不会与 PRAGMA 一起出现在扫描宏替换的标记中。

我们可以忽略LOOP_xxx宏;它们只是简单地扩展为各种标记,并且处理生成的标记就像它们正常出现在源文件中一样。我们可以只考虑 DELAY(PRAGMA)(foo)WRAP(DELAY(PRAGMA)(foo).

根据 C 2018 6.10.3.1 和 6.10.3.4,处理宏的参数以进行宏替换,然后将生成的标记替换为宏的替换标记,然后生成的标记和源文件的后续标记重新扫描以进行进一步替换。 (处理宏参数的标记时,它们被视为构成整个源文件。)

DELAY(PRAGMA)(foo)中:

  1. PRAGMAxDELAY的参数,但是后面没有括号,所以不是宏来替换。
  2. PRAGMADELAY 的替换标记 x EMPTY() 中替换了 x
  3. 扫描结果 PRAGMA EMPTY() 以进行替换。
  4. EMPTY 被替换为空。
  5. 替换 EMPTY 的结果,以及后续标记((foo),以及它后面的任何内容)都会被扫描。请注意 PRAGMA 在这些标记中是 而不是 :它不是由替换 EMPTY.
  6. 产生的标记的一部分
  7. 宏替换完成。

WRAP(PRAGMA)(foo)中,前五步相同,其余步骤结果替换为PRAGMA (foo)

  1. PRAGMAxDELAY的参数,但是后面没有括号,所以不是宏来替换。
  2. PRAGMADELAY 的替换标记 x EMPTY() 中替换了 x
  3. 扫描结果 PRAGMA EMPTY() 以进行替换。
  4. EMPTY 被替换为空。
  5. 扫描 EMPTY 替换的结果以及后续标记 ((foo))。如上,PRAGMA不在这些token中,所以不替换。
  6. WRAP 参数的宏替换已完成,生成了 PRAGMA (foo)
  7. 参数中的这些标记被代入 WRAP{ BODY },生成 { PRAGMA (foo) }.
  8. 重新扫描这些标记(以及源文件中的以下标记)以进行进一步替换。现在 PRAGMA (foo) 出现在这些标记中,所以它被替换了。