宏观扩张的奇怪结果

请考虑以下代码段

#include #define A -B #define B -C #define C 5 int main() { printf("The value of A is %d\n", A); return 0; } 

产量

 The value of A is 5 

但这根本不应该编译,因为在扩展后它应该看起来像printf("The value of A is %d\n", --5); 然后它应该给出编译错误说lvalue required。 不是吗?

传递-E选项(例如: gcc -E ac )。 这将输出预处理的源代码。

 int main() { printf("The value of A is %d\n", - -5); return 0; } 

因此它会在--5之间引入一个空格,因此它不会被视为递减运算符--因此printf将打印5。

关于令牌间距的 GCC文档提供了有关为何产生额外空间的信息:

首先,考虑一个仅涉及独立预处理器的问题:需要保证重新读取其预处理输出会产生相同的令牌流。 如果不采取特殊措施,由于宏观替代,情况可能并非如此。 例如:

  #define PLUS + #define EMPTY #define f(x) =x= +PLUS -EMPTY- PLUS+ f(=) ==> + + - - + + = = = not ==> ++ -- ++ === 

一种解决方案是简单地在所有相邻的令牌之间插入空格。 但是,我们希望将空间插入保持在最低限度,这既是出于美学原因,也是因为它仍然会导致仍然试图滥用Fortran源和Makefile等预处理器的人员出现问题。

现在,请注意,当从原始lexed标记流添加(或删除,如EMPTY示例所示)标记时,我们需要检查意外标记粘贴。 我们称之为粘贴避免。 令牌添加和删除只能由于宏扩展而发生,但在许多地方可能会发生意外粘贴:每次宏替换之前和之后,每个参数替换,以及###运算符创建的每个标记。

我不这么认为。 即使宏扩展是文本处理,也不可能跨宏边界创建令牌。 因此 – 它是-(-5) ,而不是--5 ,因为--是一个单一的标记。

预处理器在BC的扩展之间引入了一个空间:

 #define A -B #define B -C #define C 5 A 

带输出(通过cpp < test.c生成)

 # 1 "test.c" # 1 "" 1 # 1 "" 3 # 329 "" 3 # 1 "" 1 # 1 "" 2 # 1 "test.c" 2 - -5 

在C语言中,在宏转换发生之前(阶段4),程序源代码在转换的早期阶段(阶段3)被分成所谓的预处理令牌 。 稍后(在阶段7), 预处理令牌被转换为常规令牌 ,这些令牌被送入编译器本身的语法和语义分析器(参见语言规范中的“5.1.1.2翻译阶段”)。

阶段3是形成未来C语言运算符和其他词汇元素的预处理标记的阶段(标识符,数字,标点符号,字符串文字等)。形成多字符标点符号-->>=等等早期。 为了最终在第7阶段获得--运算符的令牌,你需要在第3阶段早期作为完整的标点符号。在第7阶段从预处理标记转换为常规标记时,不会发生额外的标点符号连接,这意味着在第3阶段检测到的两个相邻的标点符号将不会成为单个标记--在第7阶段。编译器本身永远不会有机会看到这两个相邻的-和单个标记--

换句话说,在C中,您不能使用预处理器通过将它们放在一起来连接它们。 这就是预处理器具有##等专用function以便于连接的原因。 而##是你必须用来将两个令牌连接成一个令牌的东西。

顺便说一句,通过声称预处理器将在你的-字符之间放置一个空格字符来解释这种行为是不正确的。 语言规范中没有类似的东西。 真正发生的是,在编译器的内部结构中,你的-令牌永远保持为两个独立的令牌。 预处理器和编译器如何实现这是它们的内部实现细节。 在具有松散耦合的预处理器和编译器本身的实现中(例如,通过中间文本表示进行通信的完全独立的模块)在相邻的标点符号之间注入空间绝对是实现所需的令牌分离的自然方式。