1.4 设计自己的编译系统
根据以上描述,我们意欲构造一个能将高级语言转化为可执行文件的编译系统。高级语言语法由我们自己定义,它可以是C语言语法,也可以是它的一个子集,但是无论如何,该高级语言由我们根据编程需要自行设计。另外,我们要求生成的可执行文件能正常执行,无论它是Linux系统的ELF可执行文件,还是Windows系统的PE文件,而本书选择生成Linux系统的ELF可执行文件。正如本章开始所描述的,我们要做的就是:自己动手完成当初单击“编译”按钮时计算机在背后做的事情。
然而在真正开工之前,我们需要承认一个事实——我们是无法实现一个像GCC那样完善的工业化编译器的。因此必须降低编译系统实现的复杂度,确保实际的工作在可控的范围内。本书对编译系统的实现做了如下修改和限制:
1)预编译的处理。如前所述,预编译作为编译前期的工作,其主要的内容在于宏命令的展开和文本替换。本质上,预编译器也需要识别源代码语义,它与编译器实现的内容十分相似。通过后面章节对编译器实现原理的介绍,我们也能学会如何构造一个简单的预编译器。因此,在高级语言的文法设计中,本书未提供与预编译处理相关的语法,而是直接对源代码进行编译,这样使得我们的精力更关注于编译器的实现细节上。
2)一遍编译的方式。编译器的设计中可以对编译器的每个模块独立设计,比如词法分析器、语法分析器、中间代码优化器等。这样做可能需要对源代码进行多遍的扫描,虽然编译效率相对较低,但是获得的源码语义信息更完善。我们设计的编译系统目标非常直接——保证编译系统输出正确的可执行文件即可,因此采用一遍编译的方式会更高效。
3)高级语言语法。为了方便大多数读者对文法分析的理解,我们参考C语言的语法格式设计自己的高级语言。不完全实现C语言的所有语法,不仅可以减少重复的工作量,还能将精力重点放在编译算法的实现上,而不是复杂的语言语法上。因此在C语言的基础上,我们删除了浮点类型和struct类型,并将数组和指针的维数简化到一维。
4)编译优化算法。编译器内引入了编译优化相关的内容,考虑到编译优化算法的多样性,我们挑选了若干经典的编译优化算法作为优化器的实现。通过对数据流问题优化算法的实现,可以帮助理解优化器的工作原理,对以后深入学习编译优化算法具有引导意义。
5)汇编语言的处理。本书的编译器产生的汇编指令属于Intel x86处理器指令集的子集,虽然这间接降低了汇编器实现的复杂度,但是不会影响汇编器关键流程的实现。另外,编译器在产生汇编代码之前已经分析了源程序的正确性,生成的汇编代码都是合法的汇编指令,因此在汇编器的实现过程中不需要考虑汇编语言的词法、语法和语义错误的情况。
6)静态链接方式。本书的编译系统实现的链接器采用静态链接的方式。这是因为动态链接器的实现相对复杂,而且其与静态链接器处理的核心问题基本相同。读者在理解了静态链接器的构造的基础上,通过进一步的学习也可以实现一个动态链接器。
7)ELF文件信息。除了ELF文件必需的段和数据,我们把代码全部存放在“.text”段,数据存储在“.data”段。按照这样的文件结构组织方式,不仅能保证二进制代码正常执行,也有助于我们更好地理解ELF文件的结构和组织。
综上所述,我们所做的限制并没有删除编译系统关键的流程。按照这样的设计,是可以允许一个人独立完成一个较为完善的编译系统的。