news 2026/10/19 8:16:16

编译原理复习提纲:从词法分析到代码生成的完整手算路线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
编译原理复习提纲:从词法分析到代码生成的完整手算路线

1. 为什么“编译原理”值得你花时间啃下来

如果你正在准备期末、考研复试,或者单纯想补一补计算机基础里最硬的那块骨头,那“编译原理复习提纲”这个标题背后要解决的事情就很明确了:把一门公认难啃的课,拆成能理解、能背、能做题、能讲清楚的模块。我见过太多同学,教材翻了三遍,词法分析勉强能画个DFA,一到语法分析就开始怀疑人生,到了语义分析和代码生成直接放弃治疗。问题不在于智商,而在于复习路径错了——编译原理不是靠“读”能读会的,它需要你按“前端—中端—后端”的流水线去拆,按“概念—算法—手算—代码”四层去练。

这篇内容适合三类人:第一类是要应付考试的学生,需要一份能直接照着走的复习路线;第二类是想真正搞懂编译器怎么工作的人,需要把零散知识点串成一条线;第三类是已经工作但想回头补基础的人,需要知道哪些知识在实际工程里真的有用。我会把整个复习过程拆成设计思路、核心细节、实操推演和问题排查四个大块,每一块都给出可以直接抄作业的步骤和手算示例。你不需要先看完一本六百页的教材再回来,跟着这条线走,边看边在纸上画,效果比干读强十倍。

先给一个整体判断:编译原理的复习,核心就五件事——正则与有限自动机、上下文无关文法与语法分析、语法制导翻译与中间代码、运行时环境与符号表、代码优化与目标代码生成。这五件事对应编译器的五个阶段,也对应考试里百分之九十的考点。把这五块吃透,剩下的边角知识都是它们的组合和变形。

2. 复习提纲的整体设计与模块拆解

2.1 按编译器流水线组织,而不是按教材章节

很多人复习编译原理喜欢按教材目录走,第一章引论、第二章词法、第三章语法……这样走最大的问题是:你学到语法分析时,已经把词法忘了一半,学到语义分析时,前面两章全糊了。我的建议是按数据流动的方向组织复习,因为编译器本身就是一个流水线:源代码进去,经过词法分析变成记号流,经过语法分析变成语法树,经过语义分析变成带注解的树,经过中间代码生成变成三地址码,最后经过优化和目标代码生成变成汇编。

你复习的时候也应该这样:先建立“输入是什么、输出是什么、中间经过哪些变换”的框架,再往每个环节里填细节。比如词法分析的输入是字符流,输出是记号流,核心工具是正则表达式和有限自动机;语法分析的输入是记号流,输出是语法树,核心工具是上下文无关文法和下推自动机。这样你每复习一块,都知道它在整个流水线里的位置,不会迷失。

2.2 每个模块按“概念—算法—手算—代码”四层推进

光有框架还不够,编译原理的难点在于它既有理论又有工程。我的经验是每个模块都要过四层:

  • 概念层:这个阶段要解决什么问题?为什么需要它?比如为什么词法分析要用正则而不用上下文无关文法?因为正则足够表达记号的结构,而且可以用有限自动机高效识别,没必要上更重的工具。
  • 算法层:核心算法是什么?比如子集构造法、LL(1)分析表的构造、LR(1)项目集的闭包运算、算符优先关系表的构造。这些算法必须能默写出来。
  • 手算层:给你一个具体的小文法或正则表达式,你能不能在纸上一步步算出DFA、分析表、语法树?这一层是考试的重灾区,也是检验你是否真懂的唯一标准。
  • 代码层:如果让你用代码实现一个简化版,你会怎么写?比如用状态机实现词法分析器,用递归下降实现语法分析。这一层不一定要写完整编译器,但至少要把核心数据结构想清楚。

2.3 重点模块的优先级排序

时间有限的情况下,不是所有内容都同等重要。根据我自己的复习和带人经验,优先级大致是这样的:

优先级模块理由
最高语法分析(LL、LR)考试分值最大,算法最复杂,最容易拉开差距
最高语法制导翻译与中间代码连接前端和后端的桥梁,大题常考
高词法分析与有限自动机基础中的基础,DFA最小化常考
高运行时环境与符号表概念多但难度不大,背了就有分
中代码优化考点相对固定,掌握常见优化即可
中目标代码生成如果考纲包含就重点看基本块和活跃变量分析
低编译器前端工具(如Lex/Yacc原理)了解即可,除非明确考

这个排序不是绝对的,如果你的考纲明确说代码优化占三十分,那当然要调整。但大多数情况下,语法分析和语法制导翻译是绝对的核心。

2.4 复习节奏建议

我试过的最有效的节奏是“三轮法”:

第一轮用两周左右,把五个模块的概念和算法过一遍,不追求全懂,但要求知道每个模块在干什么、核心算法叫什么。第二轮用一周,专门做手算题,把历年真题里出现的DFA构造、LL(1)分析表、LR项目集、语法树、三地址码全部手算一遍。第三轮用三到五天,查漏补缺,把错题和容易混淆的概念集中突破,比如LL和LR的区别、SLR和LALR的区别、语法制导定义和翻译方案的区别。

注意:不要在第一轮就死磕难题,那样会严重拖慢进度。先把框架搭起来,再填肉。

3. 核心细节解析与手算实操要点

3.1 词法分析:从正则表达式到最小DFA的完整手算流程

词法分析的核心就一句话:用正则表达式描述记号的模式,用有限自动机识别这些模式。考试里最常见的题型是:给你一个正则表达式或一组记号描述,让你构造NFA,再转成DFA,最后最小化。

我拿一个典型例子走一遍。假设要识别标识符和整数,规则是:标识符以字母或下划线开头,后跟字母、数字或下划线;整数是数字序列。对应的正则表达式可以写成:

id = [a-zA-Z_][a-zA-Z0-9_]* num = [0-9]+

第一步,构造NFA。对于id,可以画成:起始状态经过字母或下划线到状态A,状态A经过字母、数字或下划线回到状态A,状态A是接受状态。对于num类似。然后把两个NFA用ε转移连到一个新的起始状态,形成合并的NFA。

第二步,子集构造法转DFA。核心操作是计算ε闭包和move集合。具体做法是:从起始状态的ε闭包开始,对每个输入符号计算move后的ε闭包,得到新状态。重复直到没有新状态产生。这一步最容易出错的地方是忘记计算ε闭包,或者把move和闭包的顺序搞反。记住:先move,再求ε闭包。

第三步,DFA最小化。用划分法:先把状态分成接受状态和非接受状态两组,然后不断细分,直到每组内所有状态对同一输入都转移到同一组。最后每组选一个代表,画出最小DFA。

手算的时候,我建议用表格来跟踪子集构造过程,列是输入符号,行是状态子集。这样不容易漏。最小化的时候,用一张表记录每对状态是否可区分,从接受/非接受对开始标记,然后迭代。

实操心得:子集构造法得到的DFA状态数可能很多,但考试通常只要求你构造出来,不要求最小化。如果题目明确说“最小化”,那一定要做,因为最小化后的DFA才是唯一的。

3.2 语法分析:LL(1)与LR的对比与手算要点

语法分析是编译原理的重头戏,也是复习时最花时间的部分。核心就两大类:自顶向下的LL分析和自底向上的LR分析。

LL(1)分析的关键是构造FIRST集、FOLLOW集和预测分析表。手算步骤:

  1. 对每个非终结符求FIRST集。规则是:如果X是终结符,FIRST(X)={X};如果X→ε,把ε加入FIRST(X);如果X→Y1Y2...Yk,把FIRST(Y1)中非ε的元素加入,如果Y1能推出ε,继续看Y2,以此类推。
  2. 求FOLLOW集。起始符号的FOLLOW包含结束符$。对于产生式A→αBβ,把FIRST(β)中非ε的元素加入FOLLOW(B);如果β能推出ε,把FOLLOW(A)加入FOLLOW(B)。
  3. 构造预测分析表。对每个产生式A→α,对FIRST(α)中每个终结符a,把A→α填入M[A,a];如果α能推出ε,对FOLLOW(A)中每个终结符b,把A→α填入M[A,b]。
  4. 检查是否有冲突。如果同一个格子有多个产生式,就不是LL(1)文法。

LR分析更复杂,但考试里常见的是SLR(1)和LALR(1)。手算步骤:

  1. 构造增广文法,加入S'→S。
  2. 构造LR(0)项目集规范族。从I0=closure({S'→·S})开始,对每个项目集和每个文法符号计算goto,直到没有新项目集。
  3. 对SLR(1),用FOLLOW集解决归约冲突:如果项目A→α·在状态I中,对FOLLOW(A)中每个符号a,填action[I,a]=reduce A→α。
  4. 对LALR(1),需要合并同心项目集,用向前看符号解决冲突。

LL和LR的区别是考试常考的概念题。简单说:LL是自顶向下、从左到右扫描、最左推导;LR是自底向上、从左到右扫描、最右推导的逆过程。LL(1)文法一定是LR(1)文法,反之不然。LL分析器用手写递归下降或预测分析表实现,LR分析器用驱动表加栈实现。

注意:LR项目集构造时,closure和goto的定义一定要记牢。closure(I)是把所有·后面是非终结符的项目,把该非终结符的产生式加进来,点在最左边。goto(I,X)是把I中所有·后面是X的项目,把点移过X,然后求closure。

3.3 语法制导翻译:从语法树到三地址码

语法制导翻译是连接语法分析和中间代码生成的桥梁。核心概念是语法制导定义(SDD)和语法制导翻译方案(SDT)。SDD是带属性和语义规则的上下文无关文法,SDT是嵌入了语义动作的产生式。

属性分两类:综合属性是自底向上传递的,继承属性是自顶向下或从左到右传递的。考试里最常见的是用SDD计算表达式的值、构造语法树、生成三地址码。

我拿一个典型例子:表达式a+b*c的三地址码生成。假设用SDD,每个非终结符有一个综合属性code表示生成的代码,还有一个addr表示存放结果的临时变量或变量名。对于产生式E→E1+T,语义规则可以是:

E.addr = new_temp() E.code = E1.code || T.code || gen(E.addr '=' E1.addr '+' T.addr)

手算的时候,从语法树底部往上,每一步生成一条三地址码。比如a+b*c,先算b*c得到t1,再算a+t1得到t2。最终三地址码序列是:

t1 = b * c t2 = a + t1

如果是控制流语句,比如if-else和while,需要用回填技术。核心思想是先生成跳转指令但目标地址留空,等目标确定后再填回去。考试里常考的是布尔表达式的翻译和回填算法。

实操心得:三地址码的格式要统一,常见的是x = y op z、x = op y、x = y、goto L、if x relop y goto L。写的时候注意临时变量的编号不要重复,回填的时候注意链表操作。

3.4 运行时环境与符号表:概念多但容易拿分

运行时环境这块,核心概念包括:活动记录、控制栈、访问链、显示表、参数传递方式、堆和栈的管理。考试里常考的是活动记录的布局和访问链的建立。

活动记录通常包含:返回地址、动态链(指向调用者的活动记录)、访问链(指向定义该过程的直接外层过程的活动记录)、参数、局部变量、临时变量。访问链的建立规则是:如果被调用过程的嵌套深度比调用者深1,访问链指向调用者的活动记录;如果深更多,沿着调用者的访问链往上走相应步数。

符号表的核心是作用域管理。常见实现是栈式符号表:进入作用域时压入新表,退出时弹出。每个表项包含名字、类型、存储位置等信息。考试里常考的是符号表的操作和名字解析规则。

注意:访问链和动态链的区别是常考点。动态链指向调用者,用于返回;访问链指向定义外层,用于访问非局部变量。别搞混。

4. 完整实操推演:从一个小文法到三地址码

4.1 选定一个覆盖多阶段的小文法

为了把前面几块串起来,我选一个简单但覆盖多个阶段的文法。假设我们要处理赋值语句和算术表达式,文法如下:

S → id = E E → E + T | T T → T * F | F F → ( E ) | id | num

这个文法足够小,手算不累,但又覆盖了词法、语法、语法制导翻译三个环节。我带你从词法开始,一路走到三地址码。

4.2 词法分析:识别id、num和运算符

对输入x = a + b * 3,词法分析输出记号流:

<id, x> <assign, => <id, a> <plus, +> <id, b> <times, *> <num, 3>

手算的时候,你只需要按正则规则逐个扫描字符,遇到字母开始收集标识符,遇到数字收集整数,遇到运算符直接返回对应记号。注意最长匹配原则:比如==要识别成等于而不是两个赋值。

4.3 语法分析:构造语法树

用LL(1)或LR分析这个记号流,得到语法树。以x = a + b * 3为例,语法树大致是:

S / | \ id = E /|\ E + T | /|\ T T * F | | | F F num | | id id

手算语法树的时候,从起始符号开始,按产生式展开,遇到终结符就和输入记号匹配。LL是自顶向下展开,LR是自底向上归约。两种方法得到的语法树是一样的。

4.4 语法制导翻译:生成三地址码

给每个非终结符定义综合属性addr和code。对于F → num,F.addr = num.lexeme,F.code为空。对于F → id类似。对于T → T1 * F,生成T.addr = new_temp(),T.code = T1.code || F.code || gen(T.addr '=' T1.addr '*' F.addr)。对于E → E1 + T类似。

从语法树底部往上算:

  • F → id(a):addr = a
  • F → id(b):addr = b
  • F → num(3):addr = 3
  • T → F(b):addr = b
  • T → T * F:t1 = b * 3,T.addr = t1
  • E → T(a):addr = a
  • E → E + T:t2 = a + t1,E.addr = t2
  • S → id = E:x = t2

最终三地址码:

t1 = b * 3 t2 = a + t1 x = t2

4.5 代码优化:窥孔优化与常量折叠

如果b和3都是常量,可以在编译期直接算出t1 = 6,这就是常量折叠。如果t2只被使用一次,可以把t2替换掉,得到:

t1 = b * 3 x = a + t1

再进一步,如果t1也只使用一次,可以合并成x = a + b * 3。这就是窥孔优化的基本思想:在局部窗口内寻找可以改进的模式。

实操心得:手算三地址码的时候,临时变量的编号要连续,不要跳号。回填的时候,用链表把需要回填的指令串起来,目标确定后遍历链表填入。

5. 常见问题与排查技巧实录

5.1 概念混淆类问题速查

问题常见混淆正确理解
LL和LR的区别以为只是方向不同LL是自顶向下最左推导,LR是自底向上最右归约
SLR和LALR的区别以为LALR更强大LALR合并了同心项目集,可能引入归约-归约冲突,但比SLR精确
综合属性和继承属性以为继承属性就是自顶向下继承属性可以从父节点、兄弟节点或自身传递,不一定是自顶向下
语法制导定义和翻译方案以为是一回事SDD是声明式的,SDT是命令式的,SDT可以控制求值顺序
活动记录和符号表以为是一回事活动记录是运行时栈帧,符号表是编译时数据结构

5.2 手算出错的高频点

子集构造法忘记ε闭包:这是最常见的错误。记住,每次move之后必须求ε闭包,否则得到的DFA不完整。

FIRST集和FOLLOW集搞混:FIRST是“能推出什么开头”,FOLLOW是“后面能跟什么”。求FOLLOW时,只有产生式右边某个非终结符后面可能为空时,才把左边的FOLLOW加进去。

LR项目集closure算错:closure(I)中,如果项目A→α·Bβ,且B是非终结符,那么把所有B→·γ加进来。注意点在最左边,不要漏掉任何产生式。

三地址码临时变量重复:每次生成新临时变量时,编号要递增。如果手动编号,建议用t1、t2、t3这样顺序编,不要跳。

回填链表操作错误:回填时,把需要回填的指令的跳转目标字段串成链表,目标确定后遍历链表填入。注意链表头指针的保存和更新。

5.3 复习时间不够时的取舍策略

如果离考试只剩三天,我建议这样取舍:

  • 第一天:语法分析。把LL(1)分析表和SLR分析表的构造手算三遍,确保能独立完成。
  • 第二天:语法制导翻译和三地址码。把表达式和控制流的翻译各手算两遍。
  • 第三天:词法分析和运行时环境。DFA构造和最小化各做两题,活动记录和访问链背熟。

代码优化和目标代码生成如果时间不够,只看基本概念和常见优化名称,不深究算法细节。

注意:不要试图在最后一天背所有算法,那样只会什么都记不住。抓住分值最大的模块,确保能拿到的分不丢。

5.4 从考试到工程的延伸

如果你不是为了考试,而是想真正理解编译器,那复习完之后可以尝试写一个简化版的编译器。我建议从表达式计算器开始:支持加减乘除和括号,用递归下降做语法分析,直接生成三地址码并解释执行。这个项目不大,但能让你把词法、语法、语法制导翻译串起来。写完之后再扩展变量、赋值、if和while,你就有了一个微型语言的编译器前端。

实际工程里,编译原理的知识用得最多的地方是:解析配置文件、实现领域特定语言、写代码生成工具、做静态分析。比如你写一个JSON解析器,词法分析和语法分析的思想直接能用;你写一个模板引擎,语法制导翻译的思路能帮你设计得更好。

我个人在实际操作中的体会是:编译原理最难的不是算法本身,而是把算法和具体问题对应起来。你看到一个文法,要能判断它是不是LL(1),是不是LR(1),该用哪种分析方法。这种判断力只能通过大量手算来培养。我当年复习的时候,把教材上的例题全部手算了一遍,又把历年真题手算了一遍,才算真正入门。后来工作中遇到需要解析自定义格式的场景,那些手算经验直接派上了用场。

最后再分享一个小技巧:复习的时候准备一张A4纸,把五个模块的核心算法和易错点写成一页纸的速查表。考前只看这张纸,比翻书效率高得多。这张纸的内容应该包括:子集构造法的步骤、FIRST和FOLLOW的求法、LL(1)分析表构造、LR(0)项目集构造、SLR和LALR的区别、三地址码的格式、回填算法、活动记录布局、访问链建立规则。把这些浓缩到一页纸上,你就真正掌握了编译原理的骨架。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 8:16:14

网络分类与性能指标详解:从拓扑结构到带宽时延的排查实践

1. 为什么先从“网络的分类”讲起——先有框架才能谈指标我说个挺实际的现象&#xff1a;很多刚接触计算机网络的人&#xff0c;一上来就背带宽、背时延&#xff0c;结果真到排查“家里Wi-Fi为什么这么卡”的时候&#xff0c;发现根本不知道从哪下手。问题出在哪&#xff1f;缺…

作者头像 李华
网站建设 2026/10/19 8:16:03

Agent记忆增强实战:从RAG基础到检索优化

1. 我在 Agent 实战中最离不开的一个设计模式做智能体&#xff08;Agent&#xff09;开发这两年多&#xff0c;我迭代过好几版带记忆能力的系统。最初&#xff0c;我图省事&#xff0c;直接把对话历史整个塞进上下文窗口&#xff0c;用长文本硬扛&#xff1b;后来&#xff0c;上…

作者头像 李华
网站建设 2026/10/19 8:15:00

液态神经网络:连续时间动力系统如何重塑时序预测

近两年我做时序预测相关的工作时&#xff0c;越来越频繁地听到一个词&#xff1a;液态神经网络。一开始我以为这又是一个"新瓶装旧酒"的包装概念&#xff0c;后来认真读了相关论文、复现了核心模型&#xff0c;才发现它其实回到了一个非常古典的视角——把网络隐藏状…

作者头像 李华
网站建设 2026/10/19 8:14:44

智能客服系统如何重构电商服务与转化逻辑:从选型到调优的全流程指南

1. 客服不只是售后部门&#xff0c;它是转化链路里被低估的引擎先说一个我自己的经历。之前帮一家做家居百货的店铺做复盘&#xff0c;那家店月销已经做到行业前几&#xff0c;但客服团队一直维持在六个人&#xff0c;旺季直接被打崩。大促那几天平均响应时间拉到五分钟以上&am…

作者头像 李华
网站建设 2026/10/19 8:14:34

避开监控系统选型三大坑:对象、告警与存储

1. 先别急着选工具&#xff0c;想清楚这三个坑到底坑在哪很多团队聊起智能监控系统&#xff0c;第一反应就是“哪个工具最火”“哪个可视化最漂亮”“哪个社区最活跃”。我见过不少项目&#xff0c;技术负责人带着一摞对比表格&#xff0c;把主流开源监控方案逐条打分&#xff…

作者头像 李华
网站建设 2026/10/19 8:14:33

Node-RED队列门节点安装与流控实战指南

简介&#xff1a;这是一份面向Node-RED开发者与IoT系统集成工程师的实用扩展节点资源&#xff0c;聚焦消息流精细化控制场景&#xff0c;尤其适用于需缓冲、调度或节流消息的工业网关、设备告警聚合、API限流等中高级应用。资源包含22个文件&#xff0c;涵盖10张功能示意图&…

作者头像 李华