Linux嵌入式系统开发从小白到大牛
上QQ阅读APP看书,第一时间看更新

5.1 小白也要懂——gcc编译器的工作流程

首先需要了解编译器的工作流程。编译器是具备编辑代码功能并且能够翻译代码为机器码的工具。例如,C语言属于面向过程的高级编程语言,在写代码的时候用的是文本工具编辑,但是最终的文件格式必须为.C文件;Java语言属于面向对象的高级编程语言,在写代码的时候也是用文本工具编辑,但是最后的文件格式为.java文件。这里的文件格式就是编译器的编辑功能所具有的特点,即不管什么编译器,必须具备文件编辑功能,因为没有文件编辑,就没有谈译码的必要。

编译器的第二个主要功能就是翻译编辑好文件的功能,暂且将翻译过程简称为译码,这个功能也是编译器非常重要的功能。译码分为几个阶段,分别是预处理、编译、汇编、链接。经过链接后形成可执行文件,然后下载到目标系统执行。编译器的一般工作流程如图5-1所示。

图5-1 编译器的一般工作流程

为了便于大家理解编译器的工作流程,下面以gcc编译器执行一个hello.c文件为例进行介绍。首先使用vim编辑器编辑一个打印hello world的文件,文件名为hello.c,如图5-2所示。

图5-2 hello.c的源文件

1.编译器的预处理过程

以gcc编译的预处理过程为例,首先编译器会将所包含的头文件以及宏定义的值找到并替换成最终的内容,预处理阶段会将代码中注释部分自动删除。源文件被预处理后依旧是一个文本文件,但是经过预处理的文件要比源文件大得多。可以使用命令gcc-E hello.c-o hello.i来生成hel-lo.c的预处理文件,文件名为hello.i。使用vim hello.i查看hello.c的预处理文件内容,如图5-3所示。

图5-3 hello.i预处理文件

由于预处理文件关联文件和定义比较多,不必过于在乎预处理文件的内容,只需要知道预处理过程主要做的事情:第一,将所有的宏定义关键字#define删除,并且展开所有的宏定义,然后进行字符替换。第二,处理所有的条件编译指令,包括#ifdef、#ifndef、#endif等。第三,将头文件关键字#include删除,将#include指向的文件插入到该行处并删除所有注释内容。第四,添加行号和文件标识,这样在调试和编译出错的时候才知道是哪个文件的哪一行。第五,保留#pragma编译器指令,因为编译器需要使用它们。

2.编译器的编译阶段

对于gcc编译器来说,编译阶段主要目的是将预处理好的文件翻译成汇编文件的过程。对于程序来说,编译也是最重要的一个阶段,程序在编译阶段能够被检查出来是否有错误,并且显示出错误类型。而对于编译器来说,编译阶段也是编译器最复杂的处理过程,因为在这个过程中需要检查编程语言的语法、语义、词法以及优化内容等多个方面。可以使用gcc-S hello.i-o hello.s生成汇编代码,使用vim hello.s查看hello.c的汇编代码,如图5-4所示。

图5-4 hello.s汇编代码

gcc编译器在编译阶段是将预处理后的文件翻译成汇编代码,在前面章节中提到,汇编代码是最接近机器码的编程语言。除了使用gcc-S hello.i-o hello.s命令生成汇编代码以外,gcc-S hello.c-o hello.s也可以直接将hello.c文件编译成汇编代码。

3.编译器的汇编过程

对于gcc编译器来说,它的汇编过程就是将编译后形成的汇编文件转换为目标文件的过程,即通过汇编阶段生成中间目标文件。在Linux系统下可以使用gcc-c hello.s-o hello.o命令将编译后的汇编文件转换为中间目标文件。这里hello.o并不是最终执行文件,如图5-5所示。

图5-5 hello.c目标文件

汇编过程实质上将上一步的汇编代码转换成机器码,这一步产生的文件称为中间目标文件,是二进制格式。之所以叫中间目标文件,是因为这个目标文件不能够被直接运行,需要链接后才可以执行。gcc汇编过程通过as汇编器完成。

4.编译器的链接过程

链接是链接器ld把中间目标文件和相应的库链接为可执行文件。链接器ld负责将程序的中间目标文件与所需的附加文件链接起来,附加文件包括静态链接库和动态链接库。使用命令gcc hello.o-o hello将汇编后的目标文件生成可执行文件,如图5-6所示。

图5-6 hello.c可执行文件

链接过程将多个目标文件以及所需的静态库文件(.a)和动态库文件(.so)链接成最终的可执行文件。链接静态库文件称为静态链接,其特点是在编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件代码比较大,但在运行时就不再需要这些静态库文件的参与了。同样链接动态库文件称为动态链接,其特点是在编译链接时,并没有把库文件的代码加入到可执行文件中,而是在程序执行时由链接文件加载库,这样可以节省系统的开销,但是运行时需要依赖库文件的支持。

通过这一小节的学习,我们了解了gcc编译器的主要工作流程,即预处理、编译、汇编、链接四个步骤。gcc编译器在每一个步骤中都需要调用专门的工具,比如预处理就是调用cpp的预处理器完成预处理的所有操作,到了编译阶段再调用cc1编译器,cc1编译器主要功能是词法分析、语法分析、语义分析、源代码优化以及代码生成等。经过cc1编译后的文件会根据gcc编译器的执行流程调用as汇编器,将汇编文件生成中间的目标文件。最后目标文件再经过ld链接器的链接过程,将需要的静态库文件和动态库文件关联上中间目标文件,形成最终的可执行文件。

小白成长之路:了解静态库和动态库

gcc编译器在编译大的项目工程时往往需要在编译阶段导入静态库文件和动态库文件,下面就介绍这两个库文件怎么参与工作的。

1)静态库在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中,因此对应的链接方式称为静态链接。

2)动态库在程序编译时并不会被链接到目标代码中,而是在程序运行时才被载入。不同的应用程序如果调用相同的库,在内存里只需要有一份该共享库的实例,规避了空间浪费的问题。