宏观扩张的奇怪结果

时间:2017-01-25 17:01:32

标签: c macros

请考虑以下代码段

#include<stdio.h>
#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是必需的。不是吗?

4 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:4)

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

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

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

# 1 "test.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "test.c" 2



- -5

答案 2 :(得分:4)

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

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

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

Token Spacing上的GCC文档提供了有关为何产生额外空间的信息:

  

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

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

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

     

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

答案 3 :(得分:2)

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

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

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

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