CSAPP学习中所记录的笔记;
第七章 链接
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存中并执行。
链接可以执行于:
- 编译时:也就是在源代码被翻译成机器代码时;
- 加载时:也就是在程序被加载器加载到内存并执行时;
- 运行时:也就是由应用程序来执行;
在早期系统,链接是手动执行的;
在现代系统,链接是由叫做链接器的程序自动执行的;
链接器使分离编译成为可能;
学习链接的必要性:
- 理解链接器将帮助你构造大型程序;
- 理解链接器将帮助你避免一些危险的编程错误(例如,在默认情况下,错误的定义多个全局变量的程序将通过链接器,而不产生任何警告信息);
- 理解链接器将帮助你理解语言的作用域规则是如何实现的(全局变量和局部变量之间的区别?static属性的变量或者函数到底意味着什么);
- 理解链接将帮助你理解其他重要的系统概念(比如加载和运行程序、虚拟内存、分页、内存映射等);
- 理解链接将使你能够利用链接库;
7.1 编译器驱动程序
贯穿本章的示例程序:
main.c
main函数初始化一个整数数组,然后调用sum函数对数组进行求和;
1 | int sum(int *a, int n); |
sum.c
1 | int sum(int *a, int n) |
调用GCC编译以上程序:
1 | gcc -Og -o prog main.c sum.c |
以上命令等同于以下命令:
首先运行预处理器,将main.c翻译成ASCII码的中间文件main.i:
1 cpp main.c /tmp/main.i接下来,驱动程序运行C编译器,将main.i翻译成一个ASCII汇编语言文件main.s:
1 cc1 /temp/main.i -Og -o /tmp/main.s然后,驱动程序运行汇编器,它将main.s翻译成一个可重定位目标文件main.o:
1 as -o /temp/main.o /tmp/main.s驱动程序经过相同的过程生成sum.o。
最后,运行链接器程序ld,将main.o和sum.o以及一些必要的目标文件组合起来,创建一个可执行目标文件prog:
1 ld -o prog /tmp/main.o /tmp/sum.o
要运行可执行文件prog,使用以下命令:
1 | ./prog |
shell调用操作系统中一个叫做加载器的函数,它将可执行文件prog中的代码和数据复制到内存,然后将控制转移到这个程序的开头;
7.2 静态链接
像Linux LD程序这样的静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可以加载和运行的可执行目标文件作为输出。输入的可重定位目标文件由各种不同的代码和数据节组成,每一节都是一个连续的字节序列;
指令在一节中,初始化了的全局变量在另一节中,而未初始化的变量又在另外一节中;
链接器的两个主要任务:
符号解析:
目标文件定义和引用符号,每个符号对应于一个函数、一个全局变量或一个静态变量。
符号解析的目的是将每个符号引用正好和一个符号定义关联起来;
重定位:
链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这个符号的引用,使得他们指向这个内存位置;
7.3 目标文件
目标文件有三种形式:
可重定位目标文件:
包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件;
可执行目标文件:
包含二进制代码和数据,其形式可以被直接复制到内存并执行;
共享目标文件:
一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载到内存并链接;
可重定位目标文件(包括共享目标文件)是由编译器和汇编器生成的;
可执行目标文件是由链接器生成的;
关于各个系统的目标文件格式:
- Windows:可移植可执行格式(PE);
- Mac OS-X:Mach-O格式;
- Linux和Unix:可执行可链接格式(ELF);