为什么编译器不报告缺少分号?
Why doesn't the compiler report a missing semicolon?
我有这个简单的程序:
#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);
}
如所见on e.g. ideone.com这给出了一个错误:
prog.c: In function 'swap':
prog.c:12:5: error: invalid operands to binary * (have 'struct S' and 'struct S *')
*a = *b;
^
为什么编译器没有检测到丢失的分号?
注意:这个问题及其答案的动机是 this question. While there are other questions 与此类似,我没有发现任何提及 C 语言的自由形式能力的内容,这是导致此问题和相关错误的原因。
C 是一种自由格式 语言。这意味着您可以用多种方式格式化它,它仍然是一个合法的程序。
例如这样的语句
a = b * c;
可以这样写
a=b*c;
或喜欢
a
=
b
*
c
;
所以当编译器看到这些行时
temp = *a
*a = *b;
它认为它意味着
temp = *a * a = *b;
这当然不是一个有效的表达式,编译器会抱怨这个而不是缺少分号。它无效的原因是因为 a
是一个指向结构的指针,所以 *a * a
试图将一个结构实例 (*a
) 与一个指向结构的指针 (a
).
虽然编译器无法检测到丢失的分号,但它还会在错误的行上报告完全不相关的错误。注意到这一点很重要,因为无论您如何查看报告错误的行,那里都没有错误。有时像这样的问题需要您查看 前 行以查看它们是否正常且没有错误。
有时您甚至必须查看另一个文件才能找到错误。例如,如果头文件在头文件中最后一次定义结构,并且缺少终止结构的分号,则错误不会出现在头文件中,而是出现在包含头文件的文件中。
有时情况会变得更糟:如果您包含两个(或更多)头文件,并且第一个包含不完整的声明,很可能语法错误将在第二个头文件中指出。
与此相关的是后续错误的概念。一些错误,通常是由于实际上缺少分号,被报告为 多个 错误。这就是修复错误时从头开始很重要的原因,因为修复第一个错误可能会使多个错误消失。
这当然会导致一次修复一个错误和频繁的重新编译,这对于大型项目来说可能很麻烦。识别此类后续错误是经验带来的东西,在看到它们几次之后,更容易挖掘出真正的错误并在每次重新编译时修复多个错误。
Why doesn't the compiler detect the missing semicolon?
要记住三件事。
- C 中的行结尾只是普通的空格。
C 中的 *
既可以是一元运算符,也可以是二元运算符。作为一元运算符,它表示 "dereference",作为二元运算符,它表示 "multiply".
- 一元运算符和二元运算符之间的区别取决于它们出现的上下文。
这两个事实的结果就是我们解析的时候。
temp = *a /* Oops, missing a semicolon here... */
*a = *b;
第一个和最后一个 *
被解释为一元,但第二个 *
被解释为二进制。从语法的角度来看,这看起来不错。
只有当编译器尝试在运算符的操作数类型的上下文中解释运算符时,才会在解析后出现错误。
上面有一些很好的答案,但我会详细说明。
temp = *a *a = *b;
这实际上是 x = y = z;
的情况,其中 x
和 y
都分配了 z
的值。
你说的是the contents of address (a times a) become equal to the contents of b, as does temp
.
简而言之,*a *a = <any integer value>
是一个有效的陈述。如前所述,第一个 *
取消引用指针,而第二个将两个值相乘。
大多数编译器按顺序解析源文件,并报告发现错误的行。 C 程序的前 12 行可能是有效(无错误)C 程序的开始。程序的前 13 行不能。一些编译器会注意到他们遇到的东西的位置,这些东西本身并不是错误,并且在大多数情况下不会在代码的后面触发错误,但与其他东西结合起来可能无效。例如:
int foo;
...
float foo;
声明 int foo;
本身就可以了。同样声明 float foo;
。一些编译器可能会记录第一个声明出现的行号,并将信息性消息与该行相关联,以帮助程序员识别早期定义实际上是错误定义的情况。编译器还可以保留与 do
等内容关联的行号,如果关联的 while
未出现在正确的位置,则可以报告。然而,对于问题的可能位置紧接在发现错误的行之前的情况,编译器通常不会为该位置添加额外的报告。
有一部波兰电影名为“Nic Śmiesznego”(“没什么好笑的”)。以下是相关对话的摘录 from a scene,其中 确切地说明了为什么 编译器开发人员可能会有点害羞地宣布这样不计后果地放弃分号。
导演:“这个”是什么意思?!你是说这个物体在我的视野中?用你的手指指出来,因为我愿意相信我在做梦。
亚当:这个,就在这里(分)。
导演:这个?这是什么?!
亚当:你什么意思?这是一片森林。
导演:你能告诉我他妈的为什么我需要一片森林吗?
亚当: “该死的地狱”怎么来的?这里,在剧本里,它说了一片森林,它说...
导演:在剧本里?帮我在这个剧本里找。
亚当: 这里:(读到)“当他们来到路的尽头时,他们面前出现了一片森林”
导演:翻页
亚当:哦废话...
导演:读给我听
亚当:他们面前出现了一片...墓碑林。
看,通常不可能提前说出你真正指的是一片森林,而不是一片墓碑森林。
我有这个简单的程序:
#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);
}
如所见on e.g. ideone.com这给出了一个错误:
prog.c: In function 'swap': prog.c:12:5: error: invalid operands to binary * (have 'struct S' and 'struct S *') *a = *b; ^
为什么编译器没有检测到丢失的分号?
注意:这个问题及其答案的动机是 this question. While there are other questions 与此类似,我没有发现任何提及 C 语言的自由形式能力的内容,这是导致此问题和相关错误的原因。
C 是一种自由格式 语言。这意味着您可以用多种方式格式化它,它仍然是一个合法的程序。
例如这样的语句
a = b * c;
可以这样写
a=b*c;
或喜欢
a
=
b
*
c
;
所以当编译器看到这些行时
temp = *a
*a = *b;
它认为它意味着
temp = *a * a = *b;
这当然不是一个有效的表达式,编译器会抱怨这个而不是缺少分号。它无效的原因是因为 a
是一个指向结构的指针,所以 *a * a
试图将一个结构实例 (*a
) 与一个指向结构的指针 (a
).
虽然编译器无法检测到丢失的分号,但它还会在错误的行上报告完全不相关的错误。注意到这一点很重要,因为无论您如何查看报告错误的行,那里都没有错误。有时像这样的问题需要您查看 前 行以查看它们是否正常且没有错误。
有时您甚至必须查看另一个文件才能找到错误。例如,如果头文件在头文件中最后一次定义结构,并且缺少终止结构的分号,则错误不会出现在头文件中,而是出现在包含头文件的文件中。
有时情况会变得更糟:如果您包含两个(或更多)头文件,并且第一个包含不完整的声明,很可能语法错误将在第二个头文件中指出。
与此相关的是后续错误的概念。一些错误,通常是由于实际上缺少分号,被报告为 多个 错误。这就是修复错误时从头开始很重要的原因,因为修复第一个错误可能会使多个错误消失。
这当然会导致一次修复一个错误和频繁的重新编译,这对于大型项目来说可能很麻烦。识别此类后续错误是经验带来的东西,在看到它们几次之后,更容易挖掘出真正的错误并在每次重新编译时修复多个错误。
Why doesn't the compiler detect the missing semicolon?
要记住三件事。
- C 中的行结尾只是普通的空格。 C 中的
*
既可以是一元运算符,也可以是二元运算符。作为一元运算符,它表示 "dereference",作为二元运算符,它表示 "multiply".- 一元运算符和二元运算符之间的区别取决于它们出现的上下文。
这两个事实的结果就是我们解析的时候。
temp = *a /* Oops, missing a semicolon here... */
*a = *b;
第一个和最后一个 *
被解释为一元,但第二个 *
被解释为二进制。从语法的角度来看,这看起来不错。
只有当编译器尝试在运算符的操作数类型的上下文中解释运算符时,才会在解析后出现错误。
上面有一些很好的答案,但我会详细说明。
temp = *a *a = *b;
这实际上是 x = y = z;
的情况,其中 x
和 y
都分配了 z
的值。
你说的是the contents of address (a times a) become equal to the contents of b, as does temp
.
简而言之,*a *a = <any integer value>
是一个有效的陈述。如前所述,第一个 *
取消引用指针,而第二个将两个值相乘。
大多数编译器按顺序解析源文件,并报告发现错误的行。 C 程序的前 12 行可能是有效(无错误)C 程序的开始。程序的前 13 行不能。一些编译器会注意到他们遇到的东西的位置,这些东西本身并不是错误,并且在大多数情况下不会在代码的后面触发错误,但与其他东西结合起来可能无效。例如:
int foo;
...
float foo;
声明 int foo;
本身就可以了。同样声明 float foo;
。一些编译器可能会记录第一个声明出现的行号,并将信息性消息与该行相关联,以帮助程序员识别早期定义实际上是错误定义的情况。编译器还可以保留与 do
等内容关联的行号,如果关联的 while
未出现在正确的位置,则可以报告。然而,对于问题的可能位置紧接在发现错误的行之前的情况,编译器通常不会为该位置添加额外的报告。
有一部波兰电影名为“Nic Śmiesznego”(“没什么好笑的”)。以下是相关对话的摘录 from a scene,其中 确切地说明了为什么 编译器开发人员可能会有点害羞地宣布这样不计后果地放弃分号。
导演:“这个”是什么意思?!你是说这个物体在我的视野中?用你的手指指出来,因为我愿意相信我在做梦。
亚当:这个,就在这里(分)。
导演:这个?这是什么?!
亚当:你什么意思?这是一片森林。
导演:你能告诉我他妈的为什么我需要一片森林吗?
亚当: “该死的地狱”怎么来的?这里,在剧本里,它说了一片森林,它说...
导演:在剧本里?帮我在这个剧本里找。
亚当: 这里:(读到)“当他们来到路的尽头时,他们面前出现了一片森林”
导演:翻页
亚当:哦废话...
导演:读给我听
亚当:他们面前出现了一片...墓碑林。
看,通常不可能提前说出你真正指的是一片森林,而不是一片墓碑森林。