为什么这个宏被替换为20而不是10?

时间:2015-08-21 18:49:36

标签: macros c-preprocessor

1. #define NUM 10
2. #define FOO NUM
3. #undef NUM
4. #define NUM 20
5. 
6. FOO

当我只运行预处理器时,输出文件包含20.

但是,根据我的理解,预处理器只是替换文本。所以这就是我认为正在发生的事情(这显然是错误的但是很蠢):

  1. NUM定义为10.
  2. 因此,在第2行中,NUM被替换为10.所以现在我们有#34; #define FOO 10"。
  3. NUM未定义。
  4. NUM已重新定义,现在为20.
  5. 根据第2行更换FOO,这是第4行的重新定义,并且是10。
  6. 所以我认为输出应该是10而不是20.可以解释哪里出错了吗?

4 个答案:

答案 0 :(得分:64)

文本替换是在使用宏的地方完成的,而不是在你编写#define的地方。在您使用FOO时,它会将FOO替换为NUM,而NUM目前定义为20

答案 1 :(得分:57)

为了从标准中收集所有相关规范,我从评论主题中提取了这些信息,并根据草案N4527添加了C ++部分编号(规范性文本在两个标准中是相同的)。标准在这个问题上是绝对清楚的。

  1. #define预处理程序指令不进行宏替换。

      

    (C11§6.10¶7; C ++§16[cpp]¶6):除非另有说明,否则预处理指令中的预处理标记不受宏扩展的影响。

  2. 用替换文本替换宏后,将重新扫描新文本。如果在程序中该点处存在令牌的活动宏定义,则替换中的预处理器令牌将扩展为宏。

      

    (C11§6.10.3¶9; C ++§16.3[cpp.replace]¶9)形式的预处理指令

         

    # define identifier replacement-list new-line

         

    定义一个类似对象的宏,它使宏名称的每个后续实例都被构成指令其余部分的预处理标记的替换列表替换。然后重新扫描替换列表以获取更多宏名称,如下所示。

  3. #define后面的行直到宏名称的#undef或文件的末尾,宏定义处于活动状态。

      

    (C11§6.10.3.5¶1; C ++§16.3.5[cpp.scope]¶1)宏定义持续(独立于块结构),直到遇到相应的#undef指令或(如果没有) (直到预处理翻译单元结束)。翻译阶段4后,宏定义没有意义。

  4. 如果我们看一下该计划:

    #define NUM 10
    #define FOO NUM
    #undef NUM
    #define NUM 20
    FOO 
    

    我们看到第1行中NUM的宏定义完全持续到第3行。这些行中没有可替换的文本,因此从不使用该定义;因此,该计划实际上与:

    相同
    #define FOO NUM
    #define NUM 20
    FOO 
    

    在此计划的第三行,有FOO的有效定义,替换列表为NUMNUM有替换列表20FOO替换为其替换列表,使其成为NUM,然后再次扫描宏,导致NUM被其替换列表20替换。重新扫描,但没有定义的宏,因此最终结果是令牌20在转换阶段5留待处理。

答案 2 :(得分:20)

在:

FOO

预处理器会将其替换为NUM,然后它将NUM替换为当前定义的内容,即20

最初的四行相当于:

#define FOO NUM 
#define NUM 20

答案 3 :(得分:14)

C11标准说(和其他版本的C和C ++一样):

  

形式为# define identifier replacement-list new-line的预处理指令定义了一个类似于对象的宏,它使宏名称的每个后续实例都被构成指令其余部分的预处理标记的替换列表替换。然后重新扫描替换列表以获取更多宏名称,如下所示。

然而,它也在另一部分说(感谢rici指出这一点)。

  

除非另有说明,否则预处理指令中的预处理标记不受宏扩展的影响。

因此,在另一个#define指令中找到的宏名称​​的后续实例实际上是而不是替换。

您的行#define FOO NUM定义了以后找到令牌FOO时(在另一个#define指令之外!),它将被令牌NUM替换。

更换令牌后,会发生重新扫描,如果NUM本身是一个宏,则此时会替换NUM。 (如果NUM扩展为包含宏,则会扩展,等等。)

所以你的步骤顺序实际上是:

  1. NUM定义为10
  2. FOO定义为NUM
  3. NUM未定义并重新定义为20
  4. FOO扩展为NUM
  5. (重新扫描)NUM扩展为20
  6. 这种行为可以在另一个常见的预处理器技巧中看到,将宏的定义值转换为字符串:

    #define STR(X) #X
    #define STR_MACRO(X) STR(X)
    #define NUM 10
    
    puts( STR_MACRO(NUM) );     // output: 10
    

    如果我们写了puts( STR(NUM) ),那么输出就是NUM

    10的输出是可能的,因为和以前一样,第二个#define实际上并没有展开STR。因此,此代码中的步骤顺序为:

    1. STR(X)定义为#X
    2. STR_MACRO(X)定义为STR(X)
    3. NUM定义为10
    4. STR_MACRONUM都已展开;结果是puts( STR(10) );
    5. (重新扫描上次展开的结果)STR(10)已扩展为"10"
    6. (重新扫描上次扩展的结果)无法进一步扩展。