购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

4.5 编译器的输出

前面我们曾提到,典型编译器以机器码作为其输出。严格来说,这既不必要又非常见。大部分编译器的输出代码都是给定CPU不能直接执行的。有些编译器发出汇编语言源代码,这在执行前还需要由汇编器再做处理;有些编译器产生目标文件,这种文件与可执行代码相似,但不能直接执行;还有些编译器实际生成的是源代码输出,需要另一个高级语言编译器进一步处理。本节就来讨论这些五花八门的输出格式及其优缺点。

4.5.1 编译器输出高级语言代码

某些编译器实际生成另一种高级语言的源代码输出,参看图4-7。例如,许多编译器,包括最早的C++编译器,输出的都是C语言代码。事实上,这类编译器的编写者常常选择C语言代码作为其编译器的输出。

将高级语言代码作为编译器的输出有几点好处。输出是人可读的,且一般容易验证。编译器所生成的高级语言代码通常能跨平台移植。举例来说,如果某编译器的输出为C语言代码,通常就可以在不同机器上对其输出进行编译,因为多数平台都有C编译器。通过编译器输出高级语言代码,转换程序就能挂靠目标语言编译器中的优化程序,从而节省编写优化程序的时间。输出高级语言代码通常比输出其他类型的代码要容易得多。这就允许编译器的编写者创建不太复杂的代码生成器模块,而对于编译过程最复杂的部分,则托付给其他某种健壮的编译器来解决。

图4-7 编译器产生高级语言代码

当然,产生高级语言代码的方法也有若干缺点。首先也是最重要的,这种方法往往比直接产生可执行代码要花费更多的处理时间。为了产生可执行文件,还需要另有一个编译器,这在其他编译器类型中是不必要的。更糟的是,第二个编译器的输出可能又需要其他编译器或汇编器做进一步处理,这使得问题更加严重。这种方法还有一个不便之处,就是难以嵌入调试程序用到的调试信息。然而,这种方法最根本的问题是,高级语言通常是底层机器码的抽象表示,因此编译器发出的高级语言语句很难高效地映射到底层机器码。

通常情况下,输出为高级语言语句的编译器是将超高级语言(very high-level language,VHLL)转换成较底层的语言。例如,人们往往把C语言看成比较底层的高级语言,这也正是许多编译器将C语言格式作为常用输出格式的原因。有人曾尝试创建一种专门的、可移植的底层语言,但这种做法从未普及开来。可以在网上查阅这类系统的代表—“C--”项目。

若想通过分析编译器输出来编写效率高的代码,我们大概会发现,采用输出高级语言代码的编译器时是难以做到这一点的。对于标准的编译器,我们只需了解编译器生成的特定机器码语句。然而,当编译器输出高级语言代码时,学习编程卓越之道的难度相应加大—我们既得了解主语言变成高级语言语句的过程,又要知道第二个编译器是如何将代码转换成机器码的。

通常输出高级语言代码的编译器要么是为超高级语言准备的试验性编译器,要么试图将老旧语言里的过时代码转译到更新的计算机语言代码中(例如,将FORTRAN语言代码转换至C语言代码)。因此,期待这些编译器发出有效率的代码一般只是奢望。如果我们志在编写有效率的卓越代码,也许应对这类编译器敬而远之。能直接生成机器码或汇编语言代码的编译器更有可能产生小而快的可执行代码。

4.5.2 编译器输出汇编语言代码

很多编译器发出人能阅读的汇编语言源文件,而非二进制的机器码文件,如图4-8所示。这类编译器中最有名的当数FSF/GNU的GCC编译器套件,它输出的汇编语言代码可供FSF/GNU的Gas汇编器使用。就像发出高级语言代码的编译器一样,生成汇编语言源代码的编译器也有其优缺点。

图4-8 编译器产生汇编语言代码

类似于高级语言代码输出,生成汇编语言代码的主要不便在于,还得运行第二个语言转换器,即汇编器,才能生成可执行的代码。其另一个潜在的缺点是有些汇编器不允许嵌入调试元信息(meta-information),而调试元信息能够让调试器工作于原来的源代码—尽管许多汇编器支持嵌入这种信息的能力。如果编译器是向适当的汇编器发出代码,这两个缺点就会烟消云散。比如,FSF/GNU的Gas汇编器运行得很快,并且支持插入调试信息供源码级调试器使用。于是,FSF/GNU编译器不会因为生成Gas汇编语言代码而有所牺牲。

输出汇编语言代码的好处,尤其基于我们的目标考虑,就是编译器的输出容易看懂,且容易确定编译器输出哪些机器指令。事实上,我们在本书中一直都用这种机制来分析编译器输出。从编译器编写者的角度看,发出汇编代码可免于操心若干种不同的目标代码输出格式—由下层的汇编器来应付这些问题。这就能让编译器的编写者腾出心思去创建更具可移植性的编译器。如果他们想让编译器为不同操作系统生成代码,就无须将不同的目标输出格式合并到编译器。不错,要由汇编器干这种事,但只用对每个目标文件格式做一次编码,不必在每个编译器中为每种格式都编写代码。FSF/GNU编译器套件就利用了UNIX关于“通过一串小工具实现较大、较复杂任务”的哲学,将冗余降低到最小。

编译器产生汇编语言代码输出还有一个优越性:这种编译器通常允许在高级语言代码中嵌入内联的汇编语言语句。在对时间有严格要求的代码部分,可直接插入机器指令,而无须创建单独的汇编语言程序,再将其输出链接到高级语言程序。

4.5.3 编译器输出目标文件

大部分编译器将高级语言源代码转换成目标文件格式。目标文件格式是一种中间文件格式,包含机器指令、运行时期的二进制数据及一些元信息。链接器/加载器根据元信息将各个目标模块合并在一起,生成完整的可执行文件。这就使得程序员能将其主应用模块链接至库模块(library module)及其他目标模块,而库模块等目标模块已事先各自编写并编译好。

输出目标模块的好处在于,不需要单独的编译器或汇编器来将编译器的输出转换到目标码形式,这在运行编译器时会节省一点时间。然而应注意,链接器仍得处理目标文件输出,因而在编译完成后又要花费些许时间。不过链接器的处理速度通常很快,因此编译一个模块并将其链接到若干已编译好的模块,比起一次编译所有模块来形成可执行文件,还是划算得多。

目标模块为二进制文件,数据是人不能阅读的。因此,使用目标模块分析编译器输出会稍微困难一些。幸运的是,有一些工具程序能够对目标模块的输出反汇编,将其变成人可阅读的形式。即便其结果不像编译器直接输出的汇编语言代码那样易读,我们仍能在编译器发出目标文件时通过研究其输出而收获甚丰。

因为目标文件通常难以分析,于是许多编译器提供了一个选项,可供生成汇编代码而非目标码。有了这个方便的功能,分析编译器输出就容易多了,本书中我们对几种编译器都用了这个窍门。

注意: 4.6节提供目标文件组件的详细说明,主要讲述的是COFF(Common Object File Format,通用目标文件格式)。

4.5.4 编译器输出可执行文件

有些编译器直接发出可执行文件。这样的编译器通常很快就能在“编辑-编译-运行-测试-调试”周期中轮回一遍。不幸的是,其输出通常最难分析,需要动用调试器或反汇编器及大量手工操作,才能阅读这类编译器发出的机器指令。不过,这样的编译器因为周转很快而大受欢迎。本书后面将探讨如何分析这种编译器产生的可执行文件。 6ZRAkS1spF5SpobvceuTRCFHbK0hK8kszHQZ4QQigorX1qFafYxHzplSjEt81hy+

点击中间区域
呼出菜单
上一章
目录
下一章
×