【问题标题】:Why is this macro replaced as 20 instead 10?为什么这个宏被替换为 20 而不是 10?
【发布时间】:2015-11-15 19:39:02
【问题描述】:
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 根据第 2 行替换,在第 4 行重新定义之前,为 10。

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

【问题讨论】:

  • 这是 C 还是 C++ 问题?这两种语言不同,请选择一种。
  • 预处理器的精确行为在标准中,无需猜测。
  • @AlanStokes:是否尝试过从标准中解决这个问题?我是一名语言律师,但 10 分钟后我还无法证明我的行为。
  • @LightnessRacesinOrbit 这是一个公平的观点。我确实理解过一次。
  • FOO 永远不会被定义为 NUM 以外的任何东西。文本替换法则表明NUM 将是遇到NUM 时定义的任何内容。 line 6 NUM 定义为 20

标签: macros c-preprocessor


【解决方案1】:

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

【讨论】:

  • @LightnessRacesinOrbit,确定这是明确定义的。您对标准中的措辞有什么困难? AFAIR 明确指出,在定义点仅读取令牌序列,然后非常准确地描述了替换是如何在调用点发生的。
  • @JensGustedt:用回答这个问题的标准报价来证明这一点,然后我们会谈谈 :)
  • 预处理一次完成。当第 6 行被命中时,FOO 被替换为 NUM。由于这个确切的问题,预处理在 NUM 的开头重新开始。包含其他宏的宏。如果这没有随后将 NUM 替换为 20,那么调用其他宏的宏将永远不会编译
  • @LightnessRacesinOrbit: C11:6.10.3/9 "# define identifier replacement-list new-line 形式的预处理指令定义了一个类似对象的宏,它会导致宏名称的每个后续实例由构成指令其余部分的预处理标记的替换列表替换。替换列表然后重新扫描以获取更多宏名称......““那么”一词(我的重点)清楚地说明了重新扫描是在替换名称的后续实例之后。此外,6.10.3.5/1“宏定义持续......直到相应的#undef 指令”
  • @lightness:这将是 6.10 第 7 段:“除非另有说明,否则预处理指令中的预处理标记不受宏扩展的影响。”
【解决方案2】:

为了从标准中收集所有相关规范,我从评论线程中提取了此信息,并添加了基于草案 N4527 的 C++ 部分编号(两个标准中的规范文本相同)。标准在主题上是绝对明确的。

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

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

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

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

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

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

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

    (C11 §6.10.3.5¶1; C++ §16.3.5[cpp.scope] ¶1) 宏定义持续(独立于块结构)直到遇到相应的 #undef 指令或(如果没有遇到) 直到预处理翻译单元结束。宏定义在翻译阶段 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 中进行处理。

【讨论】:

  • 经过严格解释的出色答案。但我仍然不明白 [cpp]/6 中“除非另有说明”的必要性。你能详细说明一下吗?
  • @belloc:标准在#if#elif 指令以及某些#include 指令中指定宏扩展。所以不能说扩展永远不会在 pp 指令中执行。该子句的意思是,除非明确指出,否则不会在任何指令中执行扩展。
【解决方案3】:

在:

FOO

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

前四行等价于:

#define FOO NUM 
#define NUM 20

【讨论】:

    【解决方案4】:

    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
    

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

    【讨论】:

      猜你喜欢
      • 2017-10-07
      • 1970-01-01
      • 2017-09-08
      • 2019-08-17
      • 2017-02-03
      • 2012-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多