为什么编译器报告缺less分号?

我有这个简单的程序:

#include <stdio.h> struct S { int i; }; void swap(struct S *a, struct S *b) { struct S temp; temp = *a /* Oops, missing a semicolon here... */ *a = *b; *b = temp; } int main(void) { struct S a = { 1 }; struct S b = { 2 }; swap(&a, &b); } 

正如在ideone.com上看到的,这给出了一个错误:

 prog.c: In function 'swap': prog.c:12:5: error: invalid operands to binary * (have 'struct S' and 'struct S *') *a = *b; ^ 

为什么编译器检测不到缺less的分号?


注:这个问题和答案是由这个问题的动机。 虽然还有其他类似的问题 ,但是我没有发现任何提及C语言的自由格式的能力,这是造成这种错误的原因。

C是一种自由forms的语言。 这意味着你可以用很多方式来格式化它,它仍然是一个合法的程序。

比如像一个声明

 a = b * c; 

可以写成像

 a=b*c; 

或者像

 a = b * c ; 

所以当编译器看到这些行时

 temp = *a *a = *b; 

它认为它的意思

 temp = *a * a = *b; 

这当然不是一个有效的expression式,编译器会抱怨,而不是缺less分号。 这是无效的原因是因为a是一个指向结构的指针,所以*a * a试图将结构实例( *a )与指向结构( a )的指针相乘。

虽然编译器无法检测到缺less的分号,但它也会报告错误行中完全不相关的错误。 这一点很重要,因为不pipe你看报错的那一行多less,那里都没有错误。 有时像这样的问题将需要你看看以前的行,看看他们是否没有问题,没有错误。

有时甚至需要查看另一个文件才能find错误。 例如,如果一个头文件定义了一个头文件中的最后一个结构,并且缺less终止结构的分号,那么错误将不在头文件中,而是在包含头文件的文件中。

有时甚至更糟:如果包含两个(或更多)头文件,并且第一个头文件包含不完整的声明,则很可能语法错误将在第二个头文件中指示。


与此相关的是后续错误的概念。 一些错误(通常是由于实际上缺less分号)被报告为多个错误。 这就是为什么在修复错误时从头开始很重要,因为修复第一个错误可能会使多个错误消失。

这当然可以导致一次修复一个错误,并且频繁地重新编译,这对于大型项目来说可能是麻烦的。 认识到这样的后续错误是有经验的东西,而且经过几次的观察后,发现真正的错误并修复每个重新编译的错误更容易。

为什么编译器检测不到缺less的分号?

有三件事要记住。

  1. C中的行结尾只是普通的空格。
  2. *在C中既可以是一元运算符,也可以是二元运算符。 作为一元运算符,它意味着“解除引用”,作为二元运算符,它意味着“繁殖”。
  3. 一元操作符和二元操作符之间的区别是从它们被看到的上下文中确定的。

这两个事实的结果是我们parsing的时候。

  temp = *a /* Oops, missing a semicolon here... */ *a = *b; 

第一个和最后一个*被解释为一元,但第二个*被解释为二元。 从语法的angular度来看,这看起来不错。

只有当编译器试图在操作数types的上下文中解释操作符时,才parsing出错误。

上面的一些好的答案,但我会详细说明。

 temp = *a *a = *b; 

这其实是x = y = z; 其中xy都被分配了z的值。

你所说的是the contents of address (a times a) become equal to the contents of b, as does temp

总之, *a *a = <any integer value>是一个有效的语句。 正如前面指出的那样,第一个*一个指针解引用,而第二个则将两个值相乘。

大多数编译器按顺序parsing源文件,并报告发现错误的行。 C程序的前12行可能是有效(无错误)C程序的开始。 程序的前13行不能。 一些编译器会注意到他们遇到的事情的位置,这些错误本身并不是错误的,并且在大多数情况下不会在代码的后面触发错误,但是可能无法与其他事件结合使用。 例如:

 int foo; ... float foo; 

声明int foo; 本身就非常好。 同样的声明float foo; 。 一些编译器可能会logging出现第一个声明的行号,并将该信息与该行相关联,以帮助程序员确定早期定义实际上是错误的情况。 编译器也可能保持行号与do相关联while如果关联的while没有出现在正确的位置,则可以报告它们。 但是,如果问题的可能位置紧接在发现错误的行之前,编译器通常不会为该位置添加额外的报告。