【问题标题】:Why are some preprocessor macros not expanded unless they are arguments to another macro?为什么某些预处理器宏不扩展,除非它们是另一个宏的参数?
【发布时间】:2019-12-05 05:25:46
【问题描述】:

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

#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); 
  } 
}

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

在“坏”情况下,DELAY(PRAGMA) 扩展为 PRAGMA,但处理停止,PRAGMA 留在输出中。 如果您采用“坏”输出并对其进行重新预处理(使用之前定义的所有宏),它会正确展开。

唯一的区别是“好”案例DELAY(PRAGMA)WRAP 宏的参数的一部分,而“坏”案例不会将DELAY(PRAGMA) 传递给任何宏。如果在“坏”的情况下,我们改为单独使用PRAGMA,问题就解决了(就像在“再次好”的情况下一样)。

在“好”和“坏”案例中出现不同行为的原因是什么?

【问题讨论】:

    标签: c macros c-preprocessor preprocessor pragma


    【解决方案1】:

    在糟糕的情况下,您打算作为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. 宏替换完成。

    WRAP(PRAGMA)(foo)中,前五个步骤相同,其余步骤导致替换PRAGMA (foo)

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

    【讨论】:

    • > 源文件的结果标记和后续标记被重新扫描以进行进一步替换。啊,所以整个文件没有被扫描;而是从当前扩展到文件末尾的所有标记?这是有道理的,但很烦人。
    猜你喜欢
    • 1970-01-01
    • 2011-03-01
    • 2019-07-21
    • 1970-01-01
    • 1970-01-01
    • 2018-01-04
    • 2011-05-14
    • 2013-06-18
    相关资源
    最近更新 更多