首页 文章

C预处理器如何处理循环依赖?

提问于
浏览
63

我想知道 C 预处理器如何处理循环依赖(#defines) . 这是我的计划:

#define ONE TWO 
#define TWO THREE
#define THREE ONE

int main()
{
    int ONE, TWO, THREE;
    ONE = 1;
    TWO = 2;
    THREE = 3;
    printf ("ONE, TWO, THREE = %d,  %d, %d \n",ONE,  TWO, THREE);
}

这是预处理器输出 . 我无法弄清楚为什么输出是这样的 . 我想知道预处理器在这种情况下采取的各种步骤,以提供以下输出 .

# 1 "check_macro.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "check_macro.c"

int main()
{
 int ONE, TWO, THREE;
 ONE = 1;
 TWO = 2;
 THREE = 3;
 printf ("ONE, TWO, THREE = %d,  %d, %d \n",ONE, TWO, THREE);
}

我在linux 3.2.0-49-generic-pae上运行这个程序,并在gcc版本4.6.3(Ubuntu / Linaro 4.6.3-1ubuntu5)中编译 .

5 回答

  • 4

    在扩展预处理器宏时,不会扩展该宏的名称 . 所以你的三个符号都被定义为自己:

    ONE -> TWO -> THREE -> ONE (not expanded because expansion of ONE is in progress)
    TWO -> THREE -> ONE -> TWO (        "                         TWO      "        )
    THREE -> ONE -> TWO -> THREE (      "                         THREE    "        )
    

    这种行为是由C标准的§6.10.3.4(C11草案中的部分编号)设定的,尽管据我所知,该部分的措辞和编号自C89以来没有变化 . 遇到宏名称时,会将其替换为其定义(并处理 ### 预处理程序运算符,以及类似函数的宏的参数) . 然后重新扫描结果以获取更多宏(在文件的其余部分的上下文中):

    2 /如果在替换列表的扫描期间(不包括源文件的其余预处理标记)找到要替换的宏的名称,则不会替换它 . 此外,如果任何嵌套替换遇到要替换的宏的名称,则不会替换它...

    该条款接着说,任何因递归调用而未被替换的令牌都被有效地“冻结”:它永远不会被替换:

    ...这些未替换的宏名称预处理令牌不再可用于进一步替换,即使它们稍后(重新)检查在其中否则将替换该宏名称预处理令牌的上下文中 .

    最后一句所指的情况很少在实践中出现,但这是我能想到的最简单的情况:

    #define two one,two
    #define a(x) b(x)
    #define b(x,y) x,y
    a(two)
    

    结果是 one, two . 在替换 a 期间 two 扩展为 one,two ,扩展的 two 标记为完全展开 . 随后, b(one,two) 被扩展 . 这不再是 two 的替换,但 b 的第二个参数 two 已被冻结,因此它不会再次展开 .

  • 4

    您的问题可以通过出版物ISO/IEC 9899:TC2第6.10.3.4节"Rescanning and further replacement"第2段来解答,我在这里为了您的方便而引用;在将来, please consider reading the specificaftion when you have a question about the specification .

    如果在替换列表的扫描期间找到要替换的宏的名称(不包括源文件的其余预处理标记),则不会替换它 . 此外,如果任何嵌套替换遇到要替换的宏的名称,则不会替换它 . 这些未替换的宏名称预处理令牌不再可用于进一步替换,即使它们稍后(重新)检查在其中否则将替换该宏名称预处理令牌的上下文中 .

  • 76

    https://gcc.gnu.org/onlinedocs/cpp/Self-Referential-Macros.html#Self-Referential-Macros回答有关自引用宏的问题 .

    答案的关键在于,当预处理器找到自引用宏时,它根本不会扩展它们 .

    我怀疑,相同的逻辑用于防止循环定义的宏的扩展 . 否则,预处理器将处于无限扩展状态 .

  • 9

    在您的示例中,您在定义同名变量之前执行宏处理,因此无论宏处理的结果如何,您始终打印 1, 2, 3

    以下是首先定义变量的示例:

    #include <stdio.h>
    int main()
    {
        int A = 1, B = 2, C = 3;
    #define A B
    #define B C
    //#define C A
        printf("%d\n", A);
        printf("%d\n", B);
        printf("%d\n", C);
    }
    

    这打印 3 3 3 . 有点阴险,不评论 #define C A 改变了行的行为 printf("%d\n", B);

  • 17

    这里是rici'sEric Lippert's答案中描述的行为的一个很好的演示,即如果在已经扩展相同的宏的情况下再次遇到宏名称,则不会重新展开宏名称 .

    内容 test.c

    #define ONE 1, TWO
    #define TWO 2, THREE
    #define THREE 3, ONE
    
    int foo[] = {
      ONE,
      TWO,
      THREE
    };
    

    输出 gcc -E test.c (不包括初始 # 1 ... 行):

    int foo[] = {
      1, 2, 3, ONE,
      2, 3, 1, TWO,
      3, 1, 2, THREE
    };
    

    (我会将此作为评论发布,但在评论中包含大量代码块有点尴尬,所以我将其作为社区Wiki答案 . 如果您认为将更好地包含在现有答案的一部分中,请随意复制它并要求我删除这个CW版本 . )

相关问题