第二章 编译和链接
2.1被隐藏了的过程
我们知道,一个程序由源代码到可执行文件往往由这几步构成:
预处理(Prepressing)-> 编译(Compilation)-> 汇编(Assembly)-> 链接(Linking)。
如图所示,
2.1.1预编译
#include<stdio.h> int main(void) { printf("Hello World "); return 0; }
如上述文件hello.c, gcc -E hello.c -o hello.i
预编译过程主要处理哪些源代码文件中的以 "#" 开始的预编译指令,比如 #include,#define 等,主要处理规则如下:
1. 将所有的#define删除,并且展开所有的宏定义。
2. 处理所有的预编译指令,比如 "#if" "#ifdef" "#elif" "#else" "#endif"等。
3. 处理"#include"预编译指令,将被包含的文件插入到该预编译的位置。注意:这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
4. 删除所有的注释 "//" ''/* */"。
5. 添加行号和文件名标识,比如#2"hello.c"2,以便于编译时编译器产生调试用的符号信息及用于编译时产生编译错误或警告时能够显示行号
6. 保留所有的#pragma编译器指令,因为编译器需要用到它们。
经过预编译后的.i文件不包含任何宏定义(所有的宏都展开了),被包含的文件也被插入到.i文件中。
参考文献《程序员的自我修养--链接、装载与库》 P39
2.1.2编译 详情参考2.2
1,现在版本的GCC把预编译和编译合并成了一个步骤,使用一个叫做ccl的程序来完成这两个步骤。
2,gcc只是一些后台程序的包装,它会根据不同的参数要求去调用预编译程序ccl,汇编器as,链接器ld。
2.1.3汇编
1,汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。
2,所以汇编器的汇编过程相对于编译器来讲比较简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译就可以了。
3,经过预编译、编译和汇编直接输出目标文件(Object File)。
4,可调用汇编器as来完成, as hello.s –o hello.o
5,或者 gcc –c hello.s –o hello.o
6,也可以一步到位 gcc –c hello.c –o hello.o
2.1.4链接
2.2编译器做了什么 参考如下
http://www.cnblogs.com/xcywt/p/4902789.html
2.3链接器的年龄比编译器长
2.4模块拼装---静态链接
1, 人们把每个源代码模块独立编译,然后按照需要将它们“组装”起来,这个组装的过程就是链接(Linking)。
2, 链接的主要内容就是:把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确的衔接。
3, 从原理上说,链接器的工作无非就是把一些指令对其他符号地址的引用加以修正。
4, 链接过程主要包括了:地址和空间分配(Address and Storage Allocation),符号决议(Symbol Resolution)和重定位(Relocation)。
5, 最基本的静态链接如下图所示,源文件经过编译成目标文件(*.o / *.obj),目标文件和库一起链接最终形成可执行文件。
6, 假如main.c用到了另一个模块fun.c中的food()函数。
(1)编译mian.c时并不知道foo()的地址,所以暂时把这些调用foo()的指令的目标地址搁置。
(2)链接器会根据所引用的符号foo,自动去相应的fun.c模块查找foo的地址。
(3)然后将main.c中所有用到foo的指令重新修正,让它们的目标地址为真正的foo函数的地址。
7, 地址修正的过程也叫重定位(Relocation)。
8, 每个要被修正的地方叫一个重定位入口(Relocation Entry)。
参考文献《程序员的自我修养--链接、装载与库》