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

Som*_*ude 111 c freeform

我有这个简单的程序:

#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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

为什么编译器没有检测到丢失的分号?


注意:这个问题及其答案是由这个问题推动的.虽然还有其他与此类似的问题,但我没有发现任何提及C语言的自由格式容量的问题,这是导致此错误和相关错误的原因.

Som*_*ude 207

C是一种自由形式的语言.这意味着你可以通过多种方式对其进行格式化,它仍然是一个合法的程序.

例如一个声明

a = b * c;
Run Code Online (Sandbox Code Playgroud)

可写得像

a=b*c;
Run Code Online (Sandbox Code Playgroud)

或者喜欢

a
=
b
*
c
;
Run Code Online (Sandbox Code Playgroud)

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

temp = *a
*a = *b;
Run Code Online (Sandbox Code Playgroud)

它认为这意味着

temp = *a * a = *b;
Run Code Online (Sandbox Code Playgroud)

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

虽然编译器无法检测到丢失的分号,但它也会在错误的行上报告完全不相关的错误.这一点很重要,因为无论你看多少报告错误的行,都没有错误.有时这样的问题需要您查看前面的行,看看它们是否正常且没有错误.

有时你甚至不得不查看另一个文件来查找错误.例如,如果头文件是在头文件中定义的最后一个结构,并且缺少终止结构的分号,则错误将不在头文件中,而是在包含头文件的文件中.

有时它会变得更糟:如果你包含两个(或更多)头文件,第一个包含一个不完整的声明,很可能在第二个头文件中会指出语法错误.


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

这当然可以导致一次修复一个错误并频繁重新编译,这对于大型项目来说可能很麻烦.认识到这样的后续错误是经验所带来的,并且在看到它们几次之后,更容易挖掘出真正的错误并且每次重新编译时修复多个错误.

  • 在C++中,如果`operator*`重载,`temp =*a*a =*b`**可能是一个有效的表达式.(但问题标记为"C".) (14认同)
  • @ dan04:如果有人真的这样做了...... NOPE! (13认同)
  • 关于(a)从第一次报告的错误开始的建议的+1; (b)从报告错误的位置向后看.当你在报告错误之前自动查看该行时,你知道你是一个_real_程序员:-) (2认同)

plu*_*ash 26

为什么编译器没有检测到丢失的分号?

要记住三件事.

  1. C中的行结尾只是普通的空白.
  2. *在C中可以是一元运算符和二元运算符.作为一元运算符,它意味着"取消引用",作为二元运算符,它意味着"乘法".
  3. 一元和二元运算符之间的区别取决于它们的上下文.

这两个事实的结果是我们解析时.

 temp = *a    /* Oops, missing a semicolon here... */
 *a = *b;
Run Code Online (Sandbox Code Playgroud)

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

只有在编译器尝试解释其操作数类型的上下文中的运算符时才会发现错误.