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

dee*_*pak 63 c linux gcc c-preprocessor

我想知道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);
}
Run Code Online (Sandbox Code Playgroud)

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

# 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);
}
Run Code Online (Sandbox Code Playgroud)

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

ric*_*ici 76

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

ONE -> TWO -> THREE -> ONE (not expanded because expansion of ONE is in progress)
TWO -> THREE -> ONE -> TWO (        "                         TWO      "        )
THREE -> ONE -> TWO -> THREE (      "                         THREE    "        )
Run Code Online (Sandbox Code Playgroud)

这种行为是由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)
Run Code Online (Sandbox Code Playgroud)

结果是one, two.在更换期间two扩展到,并且扩展标记为完全扩展.随后,扩大了.这不再是在替换的背景下,而是已经被冻结的第二个参数,因此它不再被扩展. one,twoatwob(one,two)twotwob


Eri*_*ert 17

您的问题由出版物ISO/IEC 9899:TC2第6.10.3.4节"重新扫描和进一步更换",第2段回答,我在此引用为方便起见; 将来,如果您对规范有疑问,请考虑阅读具体说明.

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

  • 公平地说,找到并理解C标准中的答案并非易事.通过"go read the standard"逻辑,我们可以用RTFM回答与C相关的每个问题. (47认同)
  • 除了这不是一个坏问题.OP做了一些研究,包括一个编译和预处理器输出的例子,指定了编译器和系统等.并且似乎没有明显重复的问题.同样,阅读C标准并非易事.例如,您没有设法这样做.您出于某种原因引用了N1124草案到ISO 9899:1999 TC2,从那时起已被C99 + TC2,C99 + TC3草案N1256,C99 + TC3,C11,C11 + TC1取代.虽然我确定你知道通过这些修订的宏重新扫描的所有变化...... (21认同)
  • @Lundin:规范从一个目录开始,它清楚地标识了规范的哪个部分是关于宏扩展的; 我花了30秒才找到正确的段落,而且我不是C规范的专家.是的,根据我的优秀建议,人们*在对标准化语言*有疑问时实际阅读标准,此标签中的大多数不良问题都会消失.这是好事. (13认同)
  • @Lundin:就问题不好而言,我每天都会看到100倍更糟的问题,所以是的,非常好.我选择了该版本的标准,因为它很容易找到 - 它是从维基百科链接的 - 而且它是免费的,并且大多数编译器都遵守它.正如我所说,我根本不是C规范的历史或内容的专家; 我的观点是,我通过一次网络搜索和一瞥目录找到了问题的答案; 对于普通程序员来说,这并非超出可能性范围.我鼓励规范阅读在所有程序员的工具箱中. (9认同)
  • @Alice:虽然cHao也许可以更优雅地表达自己,但这一点很好.我基本上不关心库代码中具有明确规范的百行方法的正确性证明; 我担心整个操作系统,整个数据库等的正确性,在具有弱内存模型,内存不安全语言等的世界中运行.用于证明STL集合正确性的技术无法扩展到整个Windows操作系统. (2认同)

R S*_*ahu 9

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

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

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