编译器设计:变量未声明是语法错误还是语义错误?

6
这种类型的错误是在类型检查期间还是在解析输入时产生的?应该针对什么类型的错误进行处理?

2
这取决于编程语言。对于静态类型语言(例如C、Scala),我会认为它是一种语法错误,并期望在编译期间发生;而对于动态变量绑定的语言(例如JavaScript、Ruby),我会认为它是一种语义错误,并期望在运行时发生。无论如何,我投了“不具建设性”的票。 - user166390
那么在静态类型语言中,类型检查是在解析期间完成的吗? - arg21
1
解析和类型检查应该被视为整个编译过程中两个不同的阶段。然而,这取决于实现;可以想象 - 噫!- 在解析期间执行作用域规则。类型检查不仅仅是“作用域检查”:不仅必须定义绑定(甚至分配一个值),还必须兼容类型。 - user166390
好的反驳 - 我没有坚实的理由,除了它符合我的分类方式。(由于无效语法而导致的错误显然是语法错误,但是对于符合语法但在逻辑上不合理且可以早期捕获的代码生成的错误,我不感到舒服做出更强烈的断言。) - user166390
1
哦,有趣的问题。我不同意那些投票关闭它的人,这个问题与编程有关且具体。(顺便说一下,当我设计我的脚本语言时,我也碰到了完全相同的问题 - 虽然我还没有完成,但我发现将其作为“语义”运行时类型错误更容易实现。) - user529758
显示剩余8条评论
4个回答

3
我个人认为这是一种语义错误,因为您的语言解析得很好,即使您使用了以前未绑定的标识符。换句话说,语法分析仅检查程序是否形式良好。而语义分析实际上会检查您的程序是否具有有效的含义,例如绑定、作用域或类型。正如@pst所说,您可以在解析期间进行作用域检查,但这是一种实现细节。据我所知,旧编译器曾经这样做以节省时间和空间,但我认为如果您没有某些硬性的性能/内存限制,那么这种方法今天是可疑的。

我也同意这是一个语义错误,但在编译时,是否有任何语言分类(如C、Java)区分在编译时产生的句法错误和语义错误,还是它们只是作为错误列表产生? - arg21
1
我认为这是一个内部实现细节。例如,GCC似乎只报告错误列表,而不考虑它们是否是语法或语义错误。 - Alex
谢谢Alex,这对我很有帮助。我曾经认为语义错误只会在运行时报告。我的错。 - arg21
这是大多数潜在类型语言的情况 - 也被称为动态语言。 - Alex
我认为当C++(或其他语言)编译器创建抽象语法树(AST)时,它会发现变量不存在,因此将这些内容视为语法错误。在C++中,有些情况下,在给定点上类型或变量名都是有效的。还有一些情况只有类型是有效的,因此如果您提供了一个变量的名称或拼写错误的名称,则会出现语法错误。我仍然认为找不到符号是语义错误,但我理解在这种情况下“语法错误”消息背后的原理。 - Notinlist

2
该程序符合语言语法,因此在句法上是正确的。语言语法不包含任何声明“标识符必须声明”的语句,实际上也没有任何声明这样做的方法。在Algol-68项目中尝试构建这样的两级语法失败了,我不知道之后是否还有尝试过。
每个标识符的意义(如果有的话)是一个语义问题。Frank deRemer称这种问题为“静态语义”。

1
在我看来,这并不是严格意义上的语法错误,也不是语义错误。如果我要为静态类型、编译型语言(如C或C++)实现这个功能,那么我不会把检查放在解析器中(因为解析器几乎无法检查这个错误),而是放在代码生成器中(编译器的一部分,它遍历抽象语法树并将其转换为汇编代码)。所以在我看来,它处于语法和语义错误之间:它是一个与语法相关的错误,只能通过对代码执行语义分析来检查。
然而,如果我们考虑一个原始的脚本语言,在这种情况下,AST直接被执行(没有编译成字节码,也没有JIT),那么评估器/执行器函数本身就会遍历AST并找到未声明的变量——在这种情况下,它将是一个运行时错误。区别在于“AST_walk()”例程在程序生命周期的不同部分(编译时和运行时)中是否存在,这取决于语言是脚本语言还是编译型语言。

@rici,嗯,你为什么不能呢?我觉得你把抽象语法树和抽象语义图弄混了。 - user529758
那么,你如何为(a)-b绘制AST呢?它在语法中适用于两个完全不同的产生式。 - rici
如果是 CAST(TYPE a, UNARY_MINUS(TERM b)) 会怎么样? - rici
@rici AST 可以包含未定义的类型,为什么不行呢?只是编译器在尝试评估时不知道它的含义。 - user529758
显示剩余6条评论

0
在需要声明标识符的语言中,未声明标识符的程序是不完整的,因此缺少声明显然是语法错误。
通常处理这种情况的方法是将有关符号的信息纳入符号表中,以便解析器可以使用这些信息。
以下是一些标识符类型如何影响解析的示例:

C / C++

一个经典案例:
(a)-b;

根据变量 a 的不同,这可能是一个类型转换或者是一个减法操作:
#include <stdio.h>

#if TYPEDEF
typedef double a;
#else
double a = 3.0;
#endif

int main() {
  int b = 3;
  printf("%g\n", (a)-b);
  return 0;
}

因此,如果根本没有声明a,编译器必须将程序拒绝为语法上不正确的(这正是标准使用的词语)。

XML

这个很简单:

<block>Hello, world</blob>

这是不规范的XML,但它不能通过CFG检测出来。(尽管如此,所有的XML解析器都会正确地拒绝它作为不合规的。) 在HTML/SGML的情况下,由于在某些明确定义的情况下可以省略结束标签,所以解析更加棘手,但仍然是确定性的;同样,精确定义标签的声明将决定有效输入的解析,很容易提供解析方式不同的输入取决于声明。

英语

好的,不是一种编程语言。我有很多其他编程语言的例子,但我认为这个例子可能会触发其他的直觉。

考虑这两个语法正确的句子:

The sheep is in the meadow.
The sheep are in the meadow.

现在,怎么样:

The cow is in the meadow.
(*) The cow are in the meadow.

第二句话虽然含义明确,但是存在歧义(是名词还是动词出了问题?),但它显然不符合语法规则。但是为了知道这一点(以及其他类似的例子),我们必须知道sheep有一个未标记的复数形式。实际上,许多动物都有未标记的复数形式,因此我认为以下所有内容都是符合语法规则的:

The caribou are in the meadow.
The antelope are in the meadow.
The buffalo are in the meadow.

但绝对不是:

(*) The mouse are in the meadow.
(*) The bird are in the meadow.

等等。


似乎有一个普遍的误解,即因为语法分析器使用上下文无关文法解析器,所以句法分析被限制在解析上下文无关文法上。这是不正确的。

在C(及其衍生语言)的情况下,语法分析器使用符号表来帮助解析。在XML的情况下,它使用标签堆栈,在广义SGML(包括HTML)的情况下,它还使用标签声明。因此,作为整体考虑的语法分析器比CFG更强大,而CFG只是分析的一部分。

给定程序通过语法分析并不意味着它在语义上是正确的。例如,语法分析器需要知道a是否是类型,以便正确解析(a)-b,但它不需要知道转换是否实际可能,在a是类型的情况下,或者ab是否可以有意义地相减,在a是变量的情况下。这些验证可以在构建解析树后进行类型分析,但它们仍然是编译时错误。


那么你的意思是,即使语法不正确(在语法上不正确而不是语义上),由于解析器的能力不足,在语义分析期间会捕获错误,并将其视为语法错误? - arg21
@arg21:我编辑了我的答案来回应,而不是写一个很长的评论。 - rici
语义反馈对于许多编程语言来说无疑是必需的,但其存在并不能将语义错误转化为语法错误。 - user207421
@EJP:当然,好心的人可能不会共享一个字典。但我相信我在使用“语法”这个词时,与C++标准中使用的方式类似。例如,6.8(3)“消除歧义纯粹是语法上的;也就是说,在这样的语句中出现的名称的含义,超出了它们是否是类型名称的范围,通常不会被使用或更改。”表明标识符是否命名为类型是语法的。同样,7.1.3(1)“在其声明的范围内,typedef名称在语法上等同于关键字”。 - rici
@ejp。语法规则包括class-name: identifier,enum-name: identifier,template-name: identifier和其他各种标识符。很明显,C++解析器需要在解析过程中弄清楚每个标识符的类型。请看附录A中的段落,特别是A.1:“引入了新的上下文关键字......” - rici
显示剩余6条评论

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