编译器总装
复习
- 解释器与即时编译:在“提前编译”与“边跑边编译”之间取舍
- 编译器的分工:前端读懂、中端优化、后端生成
- 从源代码到可执行文件:完整的流水线
TL;DR
- 编译器把源码一步步变成可运行的机器码
- 前端读懂、中端优化、后端生成,最后链接装载
- 编译、解释、即时编译,各有取舍
- 至此,从字符到运行的整条路已经打通
正文
这是编译原理部分的最后一章。让我们像前面几个“总装”一样,把这段旅程完整地走一遍。
一段源码的一生
从你敲下一段源代码,到它真正在机器上跑起来,它经历了许多形态:
- 字符:源码首先是一串字符
- 词法单元:词法分析把它切成一个个词
- 语法树:语法分析把词组织成结构,再简化成抽象语法树
- 带类型的树:语义分析补上类型、名字等信息
- 中间表示:降低成三地址码等与机器无关的形式
- 优化后的 IR:经过常量折叠、死代码删除、循环优化等
- 汇编:后端选指令、分配寄存器、安排栈帧,生成汇编
- 目标文件:汇编器把它变成机器码加符号、重定位信息
- 可执行文件:链接器合并文件、完成重定位
- 运行:操作系统装载它,机器开始执行
绕了一大圈,我们终于回到最开始那台由逻辑门、寄存器、时钟搭起来的机器上——只不过这次,它跑的是我们自己写的程序。整部教程的前后,在这里接上了。
三种“让它跑起来”的方式
把这一部分的最后几章连起来看,让程序运行的方式主要有三种:
- 编译:提前翻译成机器码,运行快,但不灵活、且要针对平台
- 解释:运行中边读边执行,灵活,但慢
- 即时编译:运行中把热点编译成机器码,兼顾两者,但更复杂
它们没有绝对优劣,而是在灵活、速度、复杂度之间,各取一个平衡点。选择哪一种,取决于这门语言想服务什么场景。
一些不变的东西
回头看,编译原理虽然庞杂,主线却很清晰:
- 分层:前端、中端、后端各司其职,一层层的表示逐级转换
- 等价:无论怎么改写,程序的行为不能变——这是优化的底线
- 取舍:快与灵活、全局与代价、编译时间与运行性能,处处都在权衡
这些,和整部教程里反复出现的主线,是同一回事。
接下来
现在,机器已经能执行高级语言写成的软件,算法也让程序能跑得更快。但真正的软件,还要能被人阅读、修改、测试和长期维护。一个正确又高效的算法,距离一个可靠耐用的软件,还有最后一段路。
那就是整个指南的最后一个部分——软件构造。
思考题 1
回顾整个编译过程,程序大致经历了哪些形态?
思考题 2
编译、解释、即时编译三种方式,各自的取舍是什么?
小结
知识点
- 源码在编译过程中经历字符、词、语法树、IR、汇编等多种形态
- 前端读懂、中端优化、后端生成,链接与装载后运行
- 编译、解释、即时编译各有取舍
- 编译原理的主线是分层、等价与取舍
参考资料
- Wikipedia(zh):编译器:编译流程的总览
- Wikipedia(zh):编译原理:编译器构造的理论与阶段
思考题答案(仅供参考)
思考题 1
大致经历:字符 → 词法单元 → 语法树(抽象语法树)→ 带类型与名字信息的树 → 中间表示 → 优化后的中间表示 → 汇编 → 目标文件(机器码)→ 可执行文件,最后被装载运行。
思考题 2
编译提前翻译成机器码,运行快但不够灵活、需针对平台;解释在运行时边读边执行,灵活、跨平台、启动快,但通常较慢;即时编译在运行时把热点代码编译成机器码,兼顾灵活与速度,但实现更复杂、有启动预热和运行开销。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪