Chapter 4 — step_4 符号表、作用域与语义分析

对应实践Lab04 - step_4 语义分析 主要修改文件miniCompiler_lab/labs/lab04-step4/framework/student.c

到了这里,编译器已经能把源码变成 AST。但“能建树”不代表“程序合法”。一份语法完全正确的程序,仍然可能犯很多错误:使用了没定义的变量、同一作用域里重复定义变量、把不兼容的类型硬塞到一起。

这些问题已经不是词法问题,也不是语法问题。它们属于语义分析阶段。

4.1 为什么 AST 正确还不够

看下面这段代码:

int main() {
    int x = 10;
    int sum = x + undefined_var;
    int x = 20;
    return 0;
}

从 token 角度看,它很正常。
从 AST 角度看,它也完全能建出来。

但你一眼就知道它有问题:

  1. undefined_var 没有定义
  2. 同一个作用域里 x 被重复定义

这正是语义分析存在的理由。它要回答的问题不再是“这段代码像不像 C”,而是“这段代码在语言规则下是否合法”。

4.2 符号表到底解决什么问题

编译器要判断一个名字是否合法,最基本就得知道两件事:

  1. 这个名字有没有出现过
  2. 它在哪个作用域里出现过

符号表就是为这个问题准备的。

你可以把它理解成编译器在分析过程里维护的一本“登记册”。每当看到变量声明或函数定义,就把名字记进去;每当看到标识符使用,就去查这本册子。如果查不到,就是未定义;如果当前作用域里已经有同名条目,就是重复定义。

4.3 作用域为什么不能只靠一个平面表

如果程序永远没有代码块,那么一个平面链表也许勉强够用。但只要有 { ... }、函数体、条件分支里的局部声明,问题就变了。

例如:

int main() {
    int x = 1;
    {
        int x = 2;
        return x;
    }
}

这里内层的 x 并不是非法重复定义,而是一个新的、遮蔽外层名字的局部变量。
这说明编译器必须能区分“当前作用域”和“父作用域”。

所以本章的符号表不能是单张平面表,而应该是带 parent 指针的作用域链。进入新块时进入新作用域,离开时再退回父作用域。这种结构会一路影响到后面的代码生成,因为变量栈偏移本身也依赖作用域中的符号信息。

4.4 语义分析器真正做的事

语义分析并不是对整棵 AST 做一次抽象的“检查”。它更像一次带上下文的遍历:

  • 看到变量声明时,检查当前作用域能不能加这个名字
  • 看到标识符时,查符号表看它有没有定义
  • 看到赋值时,确认左边名字存在、右边表达式类型合理
  • 进入 block 时切换作用域
  • 离开 block 时恢复作用域

也就是说,语义分析的关键不在“遍历”本身,而在“遍历时手里一直带着符号表上下文”。

4.5 现在最值得看的错误输出

当语义分析发现错误时,你应当期待类似这样的输出:

语义错误 [7:19]: 未定义的变量 'undefined_var'
语义错误 [10:9]: 变量 'x' 重复定义
编译失败: 2 个语义错误

这组输出很重要,因为它第一次让你看到编译器不只是“生成东西”,还要“拒绝不合法输入”。对于真实编译器来说,这不是附加功能,而是核心职责之一。

4.6 本章 practice 边界

这一章的 practice 路径是:

  • miniCompiler_lab/labs/lab04-step4/

主要修改文件:

  • framework/student.c

验证命令:

cd labs/lab04-step4
make clean && make test

这一章 lab 会把语义分析压成三个核心动作:

  1. student_symtab_add
  2. student_symtab_lookup
  3. student_semantic_analyze

这三个动作正好覆盖了语义阶段最关键的闭环:

  • 记录名字
  • 查找名字
  • 带着符号上下文走完整棵树并统计错误

4.7 本章最容易误判的地方

这一章最常见的误判有两个。

第一种是把“父作用域里存在同名变量”也当成重复定义。
这是不对的。重复定义说的是当前作用域里重复,不是全局禁止重名。

第二种是查找变量时只查当前作用域。
这也不对。很多合法引用本来就应该向父作用域回溯。

如果这两个边界没想清楚,你的语义分析器很容易在简单样例上看起来能跑,但一遇到嵌套 block 就开始乱报错。

4.8 本章小结

从这一章开始,编译器第一次真正站到“语言规则裁判”的位置上。AST 只是结构,语义分析才决定这棵树到底能不能进入下一阶段。

后面的代码生成之所以值得信任,前提正是:输入给它的 AST 已经经过语义筛查,不再是“长得像程序”的任何东西。

4.9 下一步

现在去完成:

  1. Lab04 - step_4 语义分析
  2. 观察未定义变量和重复定义是否被正确报出
  3. 然后进入 Chapter 5 — RISC-V 代码生成