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

One*_*ero 65 macros c-preprocessor

1. #define NUM 10
2. #define FOO NUM
3. #undef NUM
4. #define NUM 20
5. 
6. FOO
Run Code Online (Sandbox Code Playgroud)

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

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

  1. NUM定义为10.
  2. 因此,在第2行中,NUM被替换为10.所以现在我们有"#define FOO 10".
  3. NUM未定义.
  4. NUM已重新定义,现在为20.
  5. 根据第2行更换FOO,第2行在第4行重新定义之前,为10.

所以我认为输出应该是10而不是20.可以解释它出错的地方吗?

Dav*_*vid 64

文本替换是在使用宏的地方完成的,而不是在你写的地方#define.在您使用的点FOO,它会替换FOONUMNUM目前定义为20.

  • @lightness:这将是6.10第7段:"除非另有说明,否则预处理指令中的预处理令牌不受宏扩展的影响." (8认同)
  • @LightnessRacesinOrbit,确定这个定义得很好.您对标准中的措辞有什么困难?AFAIR明确指出,在定义时只读取令牌序列,然后它非常精确地描述了在调用时如何进行替换. (7认同)
  • @LightnessRacesinOrbit:C11:6.10.3/9"`#define identifier replacement-list new-line`形式的预处理指令定义了一个类似于对象的宏,它使宏名称的每个*后续实例*被替换为构成指令剩余部分的预处理令牌的替换列表.替换列表是*然后*重新扫描更多宏名称......"单词"然后"(我的重点)明确指出重新扫描是*替换后*名称的后续实例.另外,6.10.3.5/1"宏定义持续......直到相应的#undef指令" (6认同)
  • 预处理一次完成.当第6行被击中时,FOO被NUM替换.由于这个确切的问题,预处理在NUM的开头再次开始.包含其他宏的宏.如果这没有随后用20替换NUM,则不会编译调用其他宏的宏 (5认同)
  • @JensGustedt:用标准报价来回答这个问题证明它,然后我们会说:) (3认同)
  • 我试图在C99中找到好的措辞,但失败了.这实际上是明确定义的吗? (2认同)
  • @rici:好的,我认为在6.10.3/9和6.10/7之间覆盖了它.:)在这个答案中得到它会很棒,这不仅仅是一个断言. (2认同)

ric*_*ici 57

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

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

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

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

    (C11§6.10.39; C++§16.3[cpp.replace]9)表格的预处理指令

    # define identifier replacement-list new-line

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

  3. 宏定义是位于有源从继线#define,直到#undef对宏名称,或该文件的结束.

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

如果我们看一下这个程序:

#define NUM 10
#define FOO NUM
#undef NUM
#define NUM 20
FOO 
Run Code Online (Sandbox Code Playgroud)

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

#define FOO NUM
#define NUM 20
FOO 
Run Code Online (Sandbox Code Playgroud)

在这个程序中,在第三行中,有用于活性定义FOO,与替换列表NUM,以及用于NUM与替换列表20.将FOO其替换为替换列表,制作它NUM,然后再次扫描宏,导致NUM替换为其替换列表20.该替换再次被重新扫描,但没有定义的宏,因此最终结果是令牌20留在翻译阶段5进行处理.


Sho*_*hoe 20

在:

FOO
Run Code Online (Sandbox Code Playgroud)

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

最初的四行相当于:

#define FOO NUM 
#define NUM 20
Run Code Online (Sandbox Code Playgroud)


M.M*_*M.M 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

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

#define STR(X) #X
#define STR_MACRO(X) STR(X)
#define NUM 10

puts( STR_MACRO(NUM) );     // output: 10
Run Code Online (Sandbox Code Playgroud)

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

输出10是可能的,因为像以前一样,第二个#define实际上并没有扩展STR.所以这段代码中的步骤顺序是:

  1. STR(X) 定义为 #X
  2. STR_MACRO(X) 定义为 STR(X)
  3. NUM 定义为 10
  4. STR_MACRO并且NUM都扩大了; 结果是puts( STR(10) );
  5. (最后扩展的重新扫描结果)STR(10)扩展到"10"
  6. (重新扫描上次扩展的结果)无法进一步扩展.