理解C中的宏
为什么以下代码的输出值为5?
#include #define A -B #define B -C #define C 5 int main() { printf("The value of A is %d\n", A); return 0; }
这是一个棘手的问题,因为它是编译器预处理器的压力测试。
取决于预处理器是编译器的集成阶段还是通过文件或管道将其输出传递给编译器的单独程序,在这种情况下是否足够小心不执行错误的令牌粘贴,您可能会得到预期的输出: 5
或者您可能会收到编译错误。
在stdio.h
的预处理内容之后,源代码扩展为:
int main() { printf("The value of A is %d\n", --5); return 0; }
但是这两个-
是单独的标记,所以取决于预处理器是否在输出中将它们分开,你可能得到一个输出5
或一个不编译的程序,因为--
不能应用于文字5
。
gcc
和clang
预处理器都正常运行并将-
与一个额外的空间分开,以防止在使用-E
命令行选项生成预处理器输出时进行标记粘贴。 它们在
扩展后输出为预处理源代码:
int main() { printf("The value of A is %d\n", - -5); return 0; }
尝试使用您自己的编译器来检查它是如何扩展源代码的。 似乎Visual Studio 2013和2015未通过测试并拒绝该程序并出现错误。
为了清楚起见,我没有说程序的行为应该依赖于编译器架构。 我希望至少有一个常见的C编译器会错误地处理这种一致性测试。 我并不感到惊讶MS Visual Studio 2013和2015测试失败。
仅在预处理器的文本输出中需要额外空间。 如果Visual Studio使用多个单独的阶段并不重要,源程序是完全有效的,并且它们无法编译它是一个BUG。
无需编译此代码,只需在其上使用gcc -E
(预处理器),看看会发生什么:
... int main() { printf("The value of A is %d\n", - -5); return 0; }
显然结果是5
(可以通过查看嵌套宏来猜测,但是一个小的预处理器测试不会受到伤害)。
(其他答案指出,一些编译器可能会处理减号的预处理,这会导致编译器错误gcc
处理得很好。)
问题确实没有意义,但我仍然决定试一试。
Visual Studio 2013和2015: error C2105: '--' needs l-value
原因是以下行:
printf("The value of A is %d\n", A);
首先被翻译成(A变成-B):
printf("The value of A is %d\n", -B);
然后进入(B成为-C);
printf("The value of A is %d\n", --C);
然后进入(C变为5):
printf("The value of A is %d\n", --5);
并且因为5不是l值,所以你不能减少它,因此错误。 看起来很合乎逻辑,知道预处理器只会做一个简单的字符串替换。
这是一个很好的例子,如何不使用预处理器。 为避免混淆,应使用括号(不仅在这种情况下)
#define A (-B) #define B (-C) #define C (5)
每个#define预处理指令将在预处理程序的环境中插入一个赋值,该变量分配给由预处理指令列表组成的值。
{A -> -B; B->-C; C->5}
是评估A
时的环境。 现在,制作A
的评估过程,我们有
A -> -B (the identifier `A` is transformed in the stream of preprocessing tokens `-B`) -B -> --C --C -> --5 -> 5
并且Prosser的算法不再评估这个,因为它没有更多的标识符。
所以,减少,
A->5
流A
在流5
被转换,并且这一个将从C-tokens中的预处理标记转换并进一步发送到C编译器。