词法分析、语法分析、语义分析
引言
编译原理是计算机科学中最为严谨、逻辑最为严密的学科之一。它研究如何构建一种特殊的程序(编译器),将人类易于编写和理解的源语言(Source Language)自动转换为机器能够直接执行或解释的目标语言(Target Language)。
这一过程不仅仅是简单的文本翻译,而是一个涉及形式语言理论、自动机理论、算法设计与优化的复杂系统工程。编译器必须像一位极其严苛的逻辑学家,对源代码进行层层剖析,确保每一个字符、每一个结构、每一处逻辑都符合预定义的规则。本文将基于核心概念,深入展开词法、语法、语义分析的全流程,并详细阐述支撑这一体系的文法理论与工程实现。
一、编译系统的宏观架构与核心概念
在深入具体阶段之前,必须明确编译系统中的基本实体及其相互关系。
1.1 语言与程序的二元对立
- 源语言与源程序:
- 源语言是程序员使用的工具,如 C, C++, Java, Python 等。它们的设计初衷是贴近人类逻辑思维,具有可读性和抽象性。
- 源程序是用源语言编写的指令集合,是编译器的输入对象。
- 目标语言与目标程序:
- 目标语言通常是机器语言(二进制指令)、汇编语言或字节码(如 JVM Bytecode)。它们贴近硬件架构,强调执行效率。
- 目标程序是编译过程的最终产物,可直接在特定硬件或虚拟机上运行。
1.2 中间语言:编译器的“通用语”
现代编译器通常不直接从源语言翻译到目标语言,而是引入中间语言(Intermediate Language, IL)或中间表示(Intermediate Representation, IR)。
- 定义:在语法分析和语义分析之后,源程序被转换为一种内部表示形式。
- 特征:
- 结构简单:去除了源语言的复杂语法糖,保留核心逻辑。
- 含义明确:每个操作符都有精确的语义定义。
- 机器无关性:IR 不依赖特定的 CPU 架构,便于进行跨平台的优化。
- 价值:
- 优化平台:可以在 IR 层级进行与机器无关的优化(如死代码消除、常量折叠、循环不变量外提)。
- 前端解耦:只需为每种源语言开发一个前端(生成 IR),为每种目标机器开发一个后端(从 IR 生成代码),极大地降低了编译器开发的复杂度( N+M 模式 vs N×M 模式)。
二、编译前端的核心三阶段
编译过程的前端主要负责“理解”源代码,将其从字符流逐步转化为富含语义的内部结构。这一过程严格遵循由浅入深、由表及里的逻辑。
2.1 词法分析(Lexical Analysis):从字符到单词
词法分析是编译的第一道关卡,也被称为扫描(Scanning)。
- 核心任务:
- 从左到右逐个字符地读取源程序字符流。
- 依据构词规则(通常由正规式描述),识别出具有独立意义的单词符号(Token)。
- 过滤掉无意义的字符(如空格、注释、换行符)。
- 输出形式:
- 一个 Token 序列。每个 Token 通常包含两部分信息:
(种别码,属性值)。- 例如:
int a = 10;可能被识别为:(KEYWORD, "int")(IDENTIFIER, "a")(OPERATOR, "=")(CONST_INT, "10")(DELIMITER, ";")
- 例如:
- 一个 Token 序列。每个 Token 通常包含两部分信息:
- 理论基础:
- 3型文法(正规文法):词法规则由正规文法描述。
- 有穷自动机(FA):词法分析器的本质是一个确定有限自动机(DFA)。状态转移图清晰地定义了如何从初始状态经过一系列字符输入到达接受状态(即识别出一个单词)。
- 工程实现工具:Lex
- Lex 是一个词法分析程序自动生成工具。
- 工作流程:
- 用户编写
.l文件,描述各类单词的正规式规则及对应的动作(C 代码)。 - Lex 读取这些规则,构造一个高效的有穷自动机。
- 生成该自动机的驱动程序(通常为
lex.yy.c),即词法分析器源码。
- 用户编写
- 优势:将复杂的正则匹配逻辑自动化,避免了手工编写状态机的繁琐和易错性。
2.2 语法分析(Syntax Analysis):从单词到结构
在获得 Token 序列后,语法分析(Parsing)负责构建程序的骨架。
- 核心任务:
- 在词法分析的基础上,将线性的 Token 序列组合成具有层次结构的语法短语。
- 识别出“程序”、“函数”、“语句块”、“表达式”等语法单位。
- 判断源程序在结构上是否正确。如果 Token 的顺序不符合语言定义的语法规则,则报语法错误。
- 输出形式:
- 抽象语法树(AST):一种树状数据结构,根节点代表整个程序,叶子节点是具体的 Token,中间节点代表语法结构(如
IfStatement,BinaryExpression)。AST 去除了括号、分号等纯语法细节,保留了逻辑结构。
- 抽象语法树(AST):一种树状数据结构,根节点代表整个程序,叶子节点是具体的 Token,中间节点代表语法结构(如
- 理论基础:
- 2型文法(上下文无关文法,CFG):编程语言的语法结构几乎全部由 CFG 描述。CFG 允许递归定义,非常适合表达嵌套结构(如括号匹配、嵌套循环)。
- 推导与归约:语法分析的过程本质上是寻找从开始符号推导出输入串的最左推导(或最右归约)的过程。
- 工程实现工具:Yacc
- Yacc (Yet Another Compiler Compiler) 是语法分析程序自动生成工具。
- 工作流程:
- 用户编写
.y文件,定义语言的上下文无关文法产生式。 - 嵌入语义动作(C 代码片段),规定在归约某条产生式时执行的操作(如构建 AST 节点、类型检查)。
- Yacc 构造一个 LALR(1) 分析表(Look-Ahead LR),生成语法分析器源码(通常为
y.tab.c)。
- 用户编写
- 语法制导翻译:Yacc 的强大之处在于它将语法分析与语义动作紧密结合,在识别语法结构的同时即可进行初步的语义处理。
2.3 语义分析(Semantic Analysis):从结构到意义
语法正确并不代表程序有意义。语义分析是编译过程中最体现“智能”的逻辑阶段。
- 核心任务:
- 对结构上正确的源程序进行上下文有关性质的审查。
- 类型检查(Type Checking):确保运算符两边的操作数类型兼容,赋值号左右类型匹配。
- 作用域分析:验证变量的声明与引用是否合法(如变量是否已声明、是否在作用域内)。
- 控制流检查:检查 break/continue 是否位于循环内,return 是否返回值等。
- 典型案例解析:
考虑以下 C 语言片段:int arr[2], b; b = arr * 10;- 语法视角:
arr是标识符,*是运算符,10是整数,;是结束符。- 结构符合
Variable = Expression ;的文法规则。 - 结论:语法分析通过,AST 构建成功。
- 语义视角:
- 查符号表得知
arr的类型是int[2](整型数组)。 - 运算符
*要求操作数为数值类型(如 int, float),但数组名arr在此语境下不能直接作为数值参与乘法(除非是指针运算,但此处语境通常报错或警告)。 - 即使允许指针运算,
arr * 10的结果类型是指针,而b是int,类型不匹配。 - 结论:语义分析报错:“不能在表达式中使用数组变量”或“赋值类型不兼容”。
- 查符号表得知
- 语法视角:
- 实现机制:
- 符号表(Symbol Table):语义分析的核心数据结构。它记录程序中所有标识符的属性(名字、类型、作用域、存储位置等)。语义分析器在遍历 AST 时,不断查询和更新符号表。
- 属性文法:一种扩展的上下文无关文法,为每个文法符号关联属性(如类型),并通过语义规则计算这些属性。
三、形式语言与文法理论的数学基石
编译原理的严谨性源于其深厚的数学基础,特别是 Noam Chomsky 提出的形式语言分级体系。
3.1 文法的形式化定义
文法 𝐺G 是描述语言语法结构的形式规则系统,定义为四元组
![]()

3.2 乔姆斯基文法 hierarchy (Chomsky Hierarchy)
Chomsky 根据对产生式 α→β 施加的限制不同,将文法分为四类,对应四种不同能力的自动机。


3.3 文法层级与编译阶段的对应关系

四、自动化工具与工程实践
现代编译器开发高度依赖自动化工具,将形式化描述直接转换为可执行代码。

五、总结与展望
编译原理不仅是构建编译器的技术指南,更是计算机科学中形式化思维的典范。
- 分层解耦的智慧:通过词法、语法、语义的严格分层,将复杂的翻译问题分解为可管理的子问题。每一层都有明确的输入输出和理论基础。
- 数学与工程的完美结合:从 Chomsky 的文法分类到自动机理论,抽象的数学概念直接转化为 Lex/Yacc 等高效的工程工具。
- 严谨性的保障:通过形式化规则,编译器能够以 100% 的确定性检测出源程序中的结构错误和类型错误,这是动态类型语言或纯统计模型(如大语言模型)难以企及的可靠性。
随着计算机体系结构的发展(如多核、GPU、异构计算)和编程语言特性的演进(如泛型、并发原语、AI 原生语法),编译原理也在不断进化。中间表示(IR)的设计更加丰富(如 LLVM IR),优化算法更加智能(如基于机器学习的优化选择),但其核心——基于形式语言理论的严谨分析框架——依然是不可动摇的基石。
理解编译原理,就是理解计算机如何“理解”人类指令的本质过程。这不仅对于开发编译器至关重要,对于深入理解程序运行机制、设计领域特定语言(DSL)、甚至优化大模型的代码生成能力,都具有不可替代的指导意义。
浙公网安备 33010602011771号