为什么预处理器不会使两个相邻的减号成为递减?

12

请考虑以下代码:

#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;
}

这里应该按以下方式进行预处理:

  1. 首先将A替换为-B
  2. 然后将B替换为-C,从而形成表达式--C
  3. 然后将C替换为5,因此表达式结果是--5

所以,结果表达式应该会产生编译错误(lvalue错误)。 但正确答案是5,那输出如何是5呢?

请帮我解决这个问题。


1
MSVC 给出编译器错误 C2105:'--'需要左值 - Weather Vane
2
在我的电脑上使用gcc 5.3.0可以正常工作。 - thomas
2
这是一个与编译器相关的问题。一些编译器会将 A 替换为 - - 5,这会使 5 被否定两次。 - Some programmer dude
从哪里来的这段代码?请直接翻译成中文,将代码原封不动地翻译过来。 - MSalters
3个回答

15

它进行预处理(注意空格):preprocesses to

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

预处理器将粘贴标记,而不是字符串。除非您使用 ## 强制标记连接,否则它不会将两个相邻的 - 标记合并为 --

#define CAT_(A,B) A##B
#define CAT(A,B) CAT_(A,B)

#define A CAT(-,B)

#define B -C
#define C 5

int main()
{
  printf("The value of A is %d\n", A); /* A is --5 here—no space */ 
  return 0;
}

printf("A的值为%d\n", - -5);为什么这个代码可以编译通过?-<空格>-5,这怎么可能是有效的表达式呢? - Rishab Shinghal
2
@user3290550,C语言语法会将其简单地解释为-(-(5))。它可以被正确地解析。 - Petr Skocik
我尝试了下面这个程序:#define A *B #define B *C #define C 5 #include <stdio.h>int main() { printf("%d\n",A); }在这个程序中,预处理过的代码中间没有空格,结果是**5。为什么只有'-'会出现这种特殊行为呢? - Rishab Shinghal
1
@user3290550 ** 不是一个标记,因此预处理器不必插入空格来保护相邻粘贴的 * 避免意外合并。在完整的运行(与编译器结合)中,标记通常会直接传递给编译器,因此这个空格插入实际上只是为了孤立的预处理器运行而存在的。 - Petr Skocik
2
这只是gcc的一个产物,它会对预处理的标记流进行后处理,以创建一个文本流,然后将其反馈回编译器。预处理器本身(翻译阶段4)生成要馈送到以下翻译阶段的标记列表。(技术上它生成了一系列标记和空格,但空格不重要且几乎立即被丢弃。) - rici

9
虽然 C 预处理器看起来像是对代码进行了查找和替换,但实际上预处理器的工作方式略有不同。在预处理器运行之前,源文件会被分割成 “预处理记号”,这些记号是单独的文本单位。例如,单个减号不被视为一个字符,而是被视为由减号组成的一个记号,而双减号则被视为由两个减号组成的一个记号。
C 预处理器启动后,它不会将每个宏都替换为宏替换的文本字面量,而是使用替换中的预处理记号序列进行替换。在这种情况下,预处理器用一个减号后跟 B 替换 A,然后用一个减号后跟 C 替换 B,最后用 5 替换 C。此处的效果是对 5 应用了两次一元减,而不是减法操作符,即使使用字面查找和替换也会生成一个导致语法错误的减法操作符。
这很有趣,因为你无法在源代码中编写两个连续的减号并将其解释为两个一元减。这仅适用于当预处理器将所有内容拼接在一起时,它已经知道正在查看两个一元减。结果的 C 代码不会被重新扫描以再次拆分成记号。
现在是法律术语:第 §5.1.1.2/7 节说,在宏替换完成之后,每个预处理记号 - 在这里有两个(两个减号) - 都会被转换为实际的记号,然后进行语法和语义分析。这意味着编译器没有机会重新扫描这些记号,将它们重新解释为单个的记号。因此,这是一种奇怪的情况,结果的记号流无法实际输入到源代码中而不改变其含义。

除非发生了那种情况,但我不相信编译器会这样做。但也许我错了? - templatetypedef
预处理器会将 --5 替换为值 5 吗?也就是说,它会计算常量表达式吗? - Paul Ogilvie
我认为上面的解释是想说两个减号不被解释为单个递减运算符,而是两个一元减号。因为在预处理之后不会进行重新扫描。以下程序证明了上述解释,因为在扫描阶段之后,预处理器就知道--是一个递减运算符而不是两个一元减号,所以该程序将会给出编译错误。#include<stdio.h> #define C 5 int main() { printf("The value of C is %d\n", --C); return 0; } - Rishab Shinghal
“这很有趣,因为你无法在源代码中连续写入两个减号并将其解释为两个一元减号。”这有点误导人。您可以通过在它们之间放置空格来连续使用两个一元减号,例如 printf("The value of A is %d\n", - -5); - user3386109
我认为那些不是相邻的减号,而是由一个空格分隔的两个减号。(唉,我说起来感觉真的很迂腐。>_<) - templatetypedef
显示剩余6条评论

0

把结果表达式想成这样:

-(-(5))

但是预处理规则表明,当替换宏时,括号不会被预处理器替换。 - Rishab Shinghal
1
其实,仔细想想,编译器并没有这样做。正在发生的事情更加复杂,与编译过程的工作方式有关。如果这个解释是正确的,它需要解释为什么编译器不会在其他宏展开表达式周围放置括号。 - templatetypedef
我只是说“把它看作是”。并没有说“编译器实际上正在这样做”。我认为这是理解它在这种情况下所做的事情的简单方法。 也许更清楚的说法是好像它在它们之间添加了一个空格? - noelicus
好的,我不这么认为。我认为更准确而不是更清晰。所以这些信息都很有用。 - noelicus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接