欢迎回来
登录你的知识库账户
忘记密码?
还没有账户?立即注册
创建账户
注册你的专属知识库
已有账户?去登录
找回密码
输入注册邮箱获取验证码
返回登录
请输入图片中的验证码以继续注册
加载中...
取消
新建收藏
手动添加你喜欢的内容
取消
编辑头像与昵称
上传新头像或修改你的显示昵称
支持 JPG/PNG,最大 2MB
取消

问题反馈

notebasewww.notebase.cn
控制台
内容库
动态
管理
账户
U
用户
--
在线
v0.8.7 · 知识库
笔记
KnowledgeBase
网络无边,知识有迹。
0笔记
0工具
30推荐

分类导航

按主题直达

编辑精选

站内用户贡献 · 真实笔记

最新收录

每日更新
继续浏览全部内容 →
>
笔记
0
加载中...
工具
0
此页用于记录用户反馈问题后的每一次改进
笔记用法

“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。

md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。

要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。

工具用法

在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。

要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。

工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。

隐藏笔记

写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。

适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。

数据安全

你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。

技术

全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。

理念

这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。

原则

不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。

更多

产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。

举报

如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。

趋势
// 点击导航加载发现
归档
// 归档为空
最近浏览
// 暂无浏览记录
发布
// 加载中...
用户发布
// 加载中...
用户管理
// 加载中...
访问统计
// 加载中...
内容审核
// 加载中...
个人信息
// 加载中...
返回首页

深入理解编译器与语言设计:从理论到实践的完整指南

2026/7/6编程开发

本文系统介绍编译器与语言设计的核心概念,涵盖词法分析、语法分析、语义分析、中间表示、代码生成和优化等关键环节,适合希望从零构建编程语言的开发者阅读。

一、为什么需要学习编译器?

编译器是将高级语言翻译成机器可执行代码的程序,它是计算机科学中理论与实践结合最紧密的领域之一。理解编译器不仅能让你更深入掌握编程语言的工作原理,还能帮助你写出更高效的代码。更重要的是,当你需要设计一门领域特定语言(DSL)或理解现代语言特性(如泛型、闭包、模式匹配)时,编译器知识是必不可少的。

二、编译器的整体结构

一个典型的编译器分为前端(Front End)和后端(Back End),中间通过中间表示(IR, Intermediate Representation)连接:

  1. 前端:负责分析源代码,理解其结构和含义,生成与机器无关的IR。
  2. 后端:将IR转换为目标机器代码,并进行与硬件相关的优化。

这种分离设计使得编译器可以轻松支持多种源语言(如C、C++、Rust)和多种目标架构(如x86、ARM、RISC-V),只需为每种语言编写前端,为每种架构编写后端,中间共享IR和优化器。

三、词法分析(Lexical Analysis)

词法分析器(Lexer)将原始字符流转换为有意义的词素(Token)序列。Token是最小的语法单元,例如关键字(if、while)、标识符(变量名)、字面量(数字、字符串)、运算符(+、-)和分隔符(括号、分号)。

实现方式:通常使用有限自动机(Finite Automaton)来识别Token。例如,识别标识符的正则表达式为 [a-zA-Z_][a-zA-Z0-9_]*,识别数字为 [0-9]+(\.[0-9]+)?。

关键细节:

  • 词法分析器需要处理空白字符和注释,这些通常被忽略。
  • 错误处理:遇到非法字符时应报告错误位置和原因。
  • 最大匹配原则:当多个规则匹配时,选择最长的匹配(如++应被识别为两个加号,而不是一个加号和一个加号)。

四、语法分析(Syntax Analysis)

语法分析器(Parser)接收Token流,根据文法规则构建抽象语法树(AST, Abstract Syntax Tree)。AST反映了程序的结构,但不包含具体的语法细节(如分号、括号)。

文法表示:通常使用上下文无关文法(CFG),例如用BNF(巴科斯范式)描述表达式:

Expr ::= Expr '+' Term | Expr '-' Term | Term
Term ::= Term '*' Factor | Term '/' Factor | Factor
Factor ::= '(' Expr ')' | NUMBER | IDENTIFIER

解析方法:

  • 自顶向下解析(如递归下降解析器):从起始符号开始,逐步展开非终结符。适合手工编写,但需要消除左递归。
  • 自底向上解析(如LR解析器):从Token开始,逐步规约为非终结符。适合自动生成(如Yacc/Bison),能处理更广泛的文法。

AST示例:表达式 3 + 4 * 5 的AST为:

+

/
3 *
/
4 5

五、语义分析(Semantic Analysis)

语义分析器检查AST是否符合语言规则,主要包括:

  • 类型检查:确保操作数的类型兼容(如不允许字符串和整数相加)。
  • 作用域解析:确保变量在使用前已被声明,且名称在作用域内唯一。
  • 控制流检查:如检查break语句是否在循环内。

符号表(Symbol Table):存储变量、函数、类型等标识符的信息(名称、类型、作用域、内存位置)。通常用哈希表实现,支持嵌套作用域。

六、中间表示(Intermediate Representation)

IR是编译器的核心抽象,它介于源代码和机器码之间,既保留了足够的语义信息以进行优化,又足够简单以方便后端生成代码。常见的IR形式包括:

  • 三地址码(Three-Address Code):每条指令最多包含三个操作数,如 t1 = a + b。
  • 静态单赋值形式(SSA, Static Single Assignment):每个变量只被赋值一次,通过φ函数处理控制流合并,极大简化了优化算法。
  • 控制流图(CFG, Control Flow Graph):将程序表示为基本块(Basic Block)组成的图,基本块是顺序执行的指令序列,控制流通过分支和循环连接。

七、代码优化(Code Optimization)

优化分为机器无关优化和机器相关优化。常见的机器无关优化:

  • 常量折叠(Constant Folding):编译时计算常量表达式,如 2 + 3 直接变为 5。
  • 死代码消除(Dead Code Elimination):删除永远不会执行或结果不被使用的代码。
  • 公共子表达式消除(Common Subexpression Elimination):避免重复计算相同的表达式,如 a = b + c; d = b + c 变为 t = b + c; a = t; d = t。
  • 循环优化:包括循环不变式外提、循环展开、强度削弱(将乘法改为加法)等。

机器相关优化利用目标架构特性,如寄存器分配(使用图着色算法)、指令调度(避免流水线停顿)、指令选择(选择最匹配的机器指令)。

八、代码生成(Code Generation)

代码生成器将IR转换为目标机器码或汇编语言。关键步骤:

  • 指令选择:将IR指令映射到目标指令集。例如,三地址码 a = b + c 可能映射为 MOV R1, b; ADD R1, c; MOV a, R1。
  • 寄存器分配:决定哪些变量存放在寄存器中,哪些存放在内存中。图着色算法是经典方法,将变量视为图中的节点,若两个变量同时活跃则连边,用K种颜色(K个寄存器)着色。
  • 指令调度:重排指令顺序以最大化流水线效率,减少停顿。

九、运行时支持(Runtime Support)

编译器还需要生成运行时支持代码,包括:

  • 内存管理:如垃圾回收(GC)或手动内存分配(malloc/free)。
  • 异常处理:生成异常表,记录try-catch块的范围和处理代码。
  • 类型信息:对于支持反射或动态类型的语言,需要保留类型元数据。

十、语言设计要点

设计一门新语言时,需要考虑:

  • 语法设计:清晰、一致、易于解析。避免歧义(如C语言的“最令人困扰的解析”问题)。
  • 类型系统:静态类型还是动态类型?强类型还是弱类型?是否支持泛型、联合类型、模式匹配?
  • 内存模型:手动管理(如C)、引用计数(如Swift)、垃圾回收(如Java、Go)还是所有权系统(如Rust)?
  • 控制流:是否支持异常、协程、异步/等待?
  • 互操作性:如何与现有系统(如C语言库)交互?

十一、实战建议

如果你想动手实现一个编译器:

  1. 选择一个简单的语言作为目标(如TinyC、Pascal子集)。
  2. 使用工具辅助:Lex/Flex生成词法分析器,Yacc/Bison生成语法分析器。
  3. 也可以手工编写递归下降解析器,更容易理解和调试。
  4. 从单遍编译器开始(不生成IR,直接生成汇编),逐步添加优化。
  5. 测试驱动:编写大量测试用例,包括边界情况和错误输入。

十二、延伸阅读与工具

  • 经典教材:《编译原理》(龙书)、《现代编译原理》(虎书)、《高级编译器设计与实现》(鲸书)。
  • 开源编译器:GCC、LLVM(Clang)。LLVM的IR设计非常优秀,值得深入学习。
  • 在线资源:Crafting Interpreters(https://craftinginterpreters.com) 是一本极佳的实践指南,教你从头实现解释器。
  • 语言实现框架:ANTLR(语法分析器生成器)、LLVM(编译器基础设施)。

原文链接

https://dthain.github.io/books/compiler/

编写使用方法
Markdown 格式 · Ctrl+Enter 确定
新建笔记
预览
数据表格
点击单元格编辑 · Tab 移动
A1fx
Sheet1
BIH1H2≡🔗</>
隐私提醒

取消
编辑工具
取消