链接的概念
链接(lingking)是将各种代码和数据片段收集并组合成为一个大一文件的过程,这个文件可以加载(复制)到内存并执行。
链接可以执行于
- 编译时,也就是在源代码被翻译成机器代码时;
- 加载时,也就是在程序被加载器(loader)加载到内存并执行时;
- 运行时;也就是由应用程序来执行。
从源文件到可执行文件
考虑两个源文件main.c和sum.c。
int sum(int *a, int n);
int aray[2] = {1, 2};
int main()
{
int val = sum(array, 2);
return val;
}
int sum(int *a, int n)
{
int i, s = 0;
for(i = 0; i < n; i++){
s += a[i];
}
}
如上图。驱动程序依次执行以下步骤:
- 首先,运行C预处理器(cpp),将C的源程序main.c翻译成一个ASCII码的中间文件main.i;
- 接下来,运行C编译器(ccl),将main.i翻译成一个ASCII码的汇编语言文件main.s;
- 然后,运行汇编器(as),将main.s翻译成一个可重定位目标文件main.o;
- 经过上述三个步骤生成sum.o;
- 最后,运行链接器(ld),将main.o和sum.o以及一些必要的系统文件组合起来,创建一个可执行目标文件prog。
各阶段的功能如下:
- 预处理主要用于C语言编译器对各种预处理命令进行处理,包括对头文件的包含、宏定义的扩展、条件编译的选择等。例如,对# include指示的处理结果,就是将对应.h文件的内容插入到源程序文件中。
- 编译会先对源程序进行词法分析、语法分析和语义分析,然后根据分析的结果进行代码优化和存储分配,最终会把C语言源程序翻译成汇编语言程序。
- 汇编的功能是将编译生成的汇编语言代码转换为机器语言代码。
- 链接的功能是将所有关联的可重定位目标文件组合起来,以生成一个可执行文件。
C语言的翻译层次
编译器
编译器将C程序转换成一种机器能理解的符号形式的汇编语言程序(assmbly language program)。
汇编器
伪指令:汇编语言指令的一个变种,通常被看作一条汇编指令。
汇编器将汇编语言程序转换成目标文件(object file),它包括机器语言指令、数据和指令正确放入内存所需要的信息。
汇编器将分支和数据传输指令中用到的标号都放入一个**符号表(symbol table)**中。
符号表:一个用来匹配标记名和指令所在内存字的地址的列表。
UNIX系统中的目标文件通常包含以下6个不同的部分:
- 目标文件头,描述目标文件其他部分的大小和位置。
- 代码段,包含机器语言代码。
- 静态数据段,包含在程序生命周期内分配的数据
- 重定位信息,标记了一些在程序加载进内存时依赖于绝对地址的指令和数据。
- 符号表,包含未定义的剩余标记,如外部引用。
- 调试信息,包含一份说明目标模块如何编译的简明描述,这样,调试器能够将机器指令关联到 C源文件,并时数据结构也变得可读。
链接器
链接器:也称链接编译器。它是一个系统程序,把各个独立汇编的机器语言程序组合起来并且解决所有未定义的标记,最后生成可执行文件。
可执行文件:一个具有目标文件格式的功能程序,不包含未解决的引用。它可以包含符号表和调试信息。
链接器的工作分3个步骤
- 将代码和数据模块象征性地放入内存。
- 决定数据和指令标签的地址。
- 修补内部和外部引用。
加载器
加载器:把目标程序装载到内存中以准备运行的系统程序。
现在可执行文件已经在磁盘中,操作系统可以将其读入内存并启动执行它。在UNIX系统中,加载器(loader)按照如下步骤工作:
- 读取可执行文件头来确定代码段和数据段的大小。
- 为正文和数据创建一个足够大的地址空间。
- 将可执行文件中的指令和数据复制到内存中。
- 把主程序的参数(如果存在)复制到栈顶。
- 初始化机器寄存器,将栈指针指向第一个空位置。
- 跳转到启动例程,它将参数复制到参数寄存器并且调用程序的main函数。当main函数返回时,启动例程通过系统调用exit终止程序。
动态链接库
动态链接库(dynamically linked library,DLL):在程序执行过程中才被链接的库例程。