第二章 编译和链接
Linux中,使用gcc编译Hello World程序的时候,只需使用简单的命令:gcc hello.c
。事实上,这个过程可以分解为四个步骤:
预处理(Prepressing)、编译(Compilation)、汇编(Assembly)、和链接(Linking)。
-
预编译:主要处理源代码中以“#”开始的预编译指令,如“#include”、“#define”“#ifdef”等。这个过程包括了删除#define,展开宏定义,处理其他预编译指令等,删除所有的注释"//,/**/",添加行号和文件名标识(便于调试时输出相关信息或编译错误警告产生行号),保留所有的#pragma编译器指令等
经过预编译后的.i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到.i文件中。
-
编译:将预处理完的文件进行一系列的词法分析、语法分析、语义分析及优化,然后生产出相应的汇编代码文件,这个过程往往是程序构建(build)的核心部分。现版本gcc把预编译和编译两个步骤合并成一个步骤。
-
汇编:将汇编代码转变成机器可以执行的指令。汇编器输出的是目标文件.o(不是可执行文件)
gcc -c hello.c -o hello.o
-
链接:。。。
静态链接:
当一个系统特别复杂的时候,我们不得不将它分割成小的系统以达到各个突破的目的。我们将每个源代码模块独立地编译,然后按照须要将它们“组装”起来,这个组装的过程就是链接(Linking)。链接器的功能本质上就是调整地址的作用,将一些指令对其他符号地址的引用加以修正。最基本的静态链接过程如下:
每个模块的源代码文件(.c)经过编译器编译成目标文件(.o/.obj),目标文件和库一起链接形成最终的可执行文件。
比如我们在程序模块main().c中使用另外一个模块func.c中的函数foo().我们在main().c模块的每一处调用foo的时候都必须确切知道foo这个函数的地址,所以它暂时将这些调用foo的指令的目标地址搁置,等待最后链接的时候由连接器去将这些指令的目标地址修正。
在连接的过程中,对其他定义在目标文件中的函数调用的指令须重新调整,对使用其他定义在其他目标文件的变量也同样如此。假设我们有个全局变量var,他在目标文件A中,我们在目标文件B中要访问这个全局变量。。。由于在编译目标文件B的时候,编译器并不知道变量var的目标地址,所以编译器在无法确定地址的情况下会将指令的目标地址置为0,等待链接器在将目标文件A和B链接起来的时候再将其修正。这个地址修正的过程也被叫做重定位(Relocation).