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

Why is this macro replaced as 20 instead 10?

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。所以现在我们有了“#define FOO 10”。
  3. NUM 未定义。
  4. NUM 被重新定义,现在是 20。
  5. FOO根据第4行重定义之前的第2行替换为10

所以我认为输出应该是 10 而不是 20。有什么可以解释哪里出了问题吗?

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

在:

FOO

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

前四行相当于:

#define FOO NUM 
#define NUM 20

C11 标准说(以及其他版本的 C 和 C++,类似说):

A preprocessing directive of the form # define identifier replacement-list new-line defines an object-like macro that causes each subsequent instance of the macro name to be replaced by the replacement list of preprocessing tokens that constitute the remainder of the directive. The replacement list is then rescanned for more macro names as specified below.

不过另一部分也说了(感谢rici指出这一点)。

The preprocessing tokens within a preprocessing directive are not subject to macro expansion unless otherwise stated.

因此,在另一个 #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

此行为可以在另一个常见的预处理器技巧中看到,将宏的定义值转换为字符串:

#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. (上次扩展的重新扫描结果)无法进一步扩展。

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

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

    (C11 §6.10¶7; C++ §16[cpp] ¶6): The preprocessing tokens within a preprocessing directive are not subject to macro expansion unless otherwise stated.

  2. 宏被其替换文本替换后,将重新扫描新文本。如果程序中该点的标记有活动的宏定义,则替换中的预处理器标记将扩展为宏。

    (C11 §6.10.3¶9; C++ §16.3[cpp.replace] ¶9) A preprocessing directive of the form

    <b># define</b> <i>identifier replacement-list new-line</i>

    defines an object-like macro that causes each subsequent instance of the macro name to be replaced by the replacement list of preprocessing tokens that constitute the remainder of the directive. The replacement list is then rescanned for more macro names as specified below.

  3. 宏定义从 #define 之后的行开始激活,直到宏名称的 #undef 或文件末尾。

    (C11 §6.10.3.5¶1; C++ §16.3.5[cpp.scope] ¶1) A macro definition lasts (independent of block structure) until a corresponding #undef directive is encountered or (if none is encountered) until the end of the preprocessing translation unit. Macro definitions have no significance after translation phase 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,替换列表 20. FOO 被它的替换列表替换,成为 NUM,然后再次扫描它的宏,导致 NUM 被它的替换列表 20 替换。那个替换又是重新扫描,但没有定义宏,所以最终结果是令牌 20 留给翻译阶段 5 处理。