Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

编译器总装

复习

  • 解释器与即时编译:在“提前编译”与“边跑边编译”之间取舍
  • 编译器的分工:前端读懂、中端优化、后端生成
  • 从源代码到可执行文件:完整的流水线

TL;DR

  • 编译器把源码一步步变成可运行的机器码
  • 前端读懂、中端优化、后端生成,最后链接装载
  • 编译、解释、即时编译,各有取舍
  • 至此,从字符到运行的整条路已经打通

正文

  这是编译原理部分的最后一章。让我们像前面几个“总装”一样,把这段旅程完整地走一遍。

一段源码的一生

  从你敲下一段源代码,到它真正在机器上跑起来,它经历了许多形态:

  1. 字符:源码首先是一串字符
  2. 词法单元:词法分析把它切成一个个词
  3. 语法树:语法分析把词组织成结构,再简化成抽象语法树
  4. 带类型的树:语义分析补上类型、名字等信息
  5. 中间表示:降低成三地址码等与机器无关的形式
  6. 优化后的 IR:经过常量折叠、死代码删除、循环优化等
  7. 汇编:后端选指令、分配寄存器、安排栈帧,生成汇编
  8. 目标文件:汇编器把它变成机器码加符号、重定位信息
  9. 可执行文件:链接器合并文件、完成重定位
  10. 运行:操作系统装载它,机器开始执行

  绕了一大圈,我们终于回到最开始那台由逻辑门、寄存器、时钟搭起来的机器上——只不过这次,它跑的是我们自己写的程序。整部教程的前后,在这里接上了。

三种“让它跑起来”的方式

  把这一部分的最后几章连起来看,让程序运行的方式主要有三种:

  • 编译:提前翻译成机器码,运行快,但不灵活、且要针对平台
  • 解释:运行中边读边执行,灵活,但慢
  • 即时编译:运行中把热点编译成机器码,兼顾两者,但更复杂

  它们没有绝对优劣,而是在灵活、速度、复杂度之间,各取一个平衡点。选择哪一种,取决于这门语言想服务什么场景。

一些不变的东西

  回头看,编译原理虽然庞杂,主线却很清晰:

  • 分层:前端、中端、后端各司其职,一层层的表示逐级转换
  • 等价:无论怎么改写,程序的行为不能变——这是优化的底线
  • 取舍:快与灵活、全局与代价、编译时间与运行性能,处处都在权衡

  这些,和整部教程里反复出现的主线,是同一回事。

接下来

  现在,机器已经能执行高级语言写成的软件,算法也让程序能跑得更快。但真正的软件,还要能被人阅读、修改、测试和长期维护。一个正确又高效的算法,距离一个可靠耐用的软件,还有最后一段路。

  那就是整个指南的最后一个部分——软件构造

思考题 1

  回顾整个编译过程,程序大致经历了哪些形态?

思考题 2

  编译、解释、即时编译三种方式,各自的取舍是什么?

小结

知识点

  • 源码在编译过程中经历字符、词、语法树、IR、汇编等多种形态
  • 前端读懂、中端优化、后端生成,链接与装载后运行
  • 编译、解释、即时编译各有取舍
  • 编译原理的主线是分层、等价与取舍

参考资料

  1. Wikipedia(zh):编译器:编译流程的总览
  2. Wikipedia(zh):编译原理:编译器构造的理论与阶段

思考题答案(仅供参考)

思考题 1

  大致经历:字符 → 词法单元 → 语法树(抽象语法树)→ 带类型与名字信息的树 → 中间表示 → 优化后的中间表示 → 汇编 → 目标文件(机器码)→ 可执行文件,最后被装载运行。

思考题 2

  编译提前翻译成机器码,运行快但不够灵活、需针对平台;解释在运行时边读边执行,灵活、跨平台、启动快,但通常较慢;即时编译在运行时把热点代码编译成机器码,兼顾灵活与速度,但实现更复杂、有启动预热和运行开销。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪