为什么这些连续的宏替换不会导致错误?

该程序将输出设为5.但是在替换所有宏之后,它将导致--5 。 这应该导致编译错误,试图减少5 。 但它编译并运行良好。

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

为什么没有错误?

以下是汇编语句printf("The value of A is %d\n", A);的步骤printf("The value of A is %d\n", A);

  • 词法解析器产生预处理标记printf("The value of A is %dn" A);
  • A是一个扩展为2个令牌的宏-B
  • B也是一个宏,并扩展到-C
  • C又是一个宏,并扩展到5
  • 然后将标记转换为C标记,从而为预处理标记产生错误,这些标记不会转换为适当的C标记(例如: 0a )。 在此示例中,令牌是相同的。
  • 编译器根据C语法解析生成的序列: printf("The value of A is %d\n" --5) ; 匹配对printf的函数调用,带有2个参数:格式字符串和常量表达式- - 5 ,在编译时计算结果为5
  • 因此代码等同于printf("The value of A is %d\n", 5); 。 它会产生输出:

     The value of A is 5 

这个宏序列扩展为标记,而不是严格的字符序列,因此A不会扩展为- -5 ,而是扩展为- -5 。 好的C编译器在将源预处理到文本输出时会插入额外的空间,以确保生成的文本在重新分析时产生相同的标记序列。 但是请注意,C标准没有说明对文本输出的预处理,它只指定预处理作为解析阶段之一,并且编译器在预处理到文本输出时不会引入潜在的副作用,这是一个实现质量问题。

有一个单独的function可以将令牌组合到预处理器中称为令牌粘贴的新令牌中。 它需要一个特定的操作符##并且使用起来非常棘手。

另请注意,应使用围绕每个参数的括号定义宏,并围绕整个扩展使用括号以防止运算符优先级问题:

 #define A (-B) #define B (-C) #define C 5 

两个连续的破折号不会组合成单个预减量运算符--因为C预处理器可以处理单个标记,有效地在宏替换周围插入空格。 通过gcc -E运行该程序

 #define A -B #define B -C #define C 5 int main() { return A; } 

产生以下输出:

 int main() { return - -5; } 

注意第一个之后的空间-

根据标准,宏替换在预处理器令牌级别执行,而不是在单个字符级别执行(6.10.3.9):

表单的预处理指令

 # define identifier replacement-list new-line 

定义一个类似于对象的宏,它使宏名称的每个后续实例被构成指令其余部分的预处理标记的替换列表替换。

因此,两个破折号-构成两个不同的标记,因此它们在预处理器的输出中保持彼此分离。

每当我们在C程序中使用#include时,编译器将使用其值在任何地方替换变量。

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

因此,当我们打印A时,它将按以下步骤执行。

A => – 乙

B => – Visual C

A => – ( – C)=> C ^

因此,当我们打印A的值时,它出来是5。

通常,这些#define语句用于声明在代码中使用的常量值。

有关更多信息,请参阅#define指令上的此链接