链接:
将各种代码段和数据段收集组合成为一个单一文件的过程。
链接的执行过程可以发生在三种时期:
- 编译期:源代码翻译成机器代码的过程中。
- 加载期:程序被内核加载器加载到内存时。
- 运行期:应用程序来控制执行。
链接器的出现使得构建大型软件系统成为可能,因为它使得各个模块可以单独完成编译。当需要改进模块时,单独的修改相应的模块并编译,通过链接器重新链接生成新版本的软件系统。
根据链接的发生时期不同分为以下三类:
- 传统静态链接
- 加载时共享库动态链接
- 运行时共享库动态链接
一个典型的链接过程:
sum.cpp中定义一个函数
int sum(int a,int b)
{
return a+b;
}
main.cpp中声明并使用
int sum(int,int);
int main()
{
int a=1;
int b=2;
int c=sum(a,b);
}
编译系统通常包含预处理器、编译器、汇编器、链接器。通过这些不同部分完成程序从源代码到最终可执行程序的过程。
GNU编译系统对上述程序的过程为:
g++ main.cpp sum.cpp -o prog
- main.cpp=>main.o
- sum.cpp=>sum.o
- ld链接器:sum.o+main.o=》完成链接的可执行程序prog
Linux LD链接器以一组可重定位的目标文件(.o)为输入,指定相应的参数,生成一个完全链接,可以加载和运行的可执行目标文件(即:可执行文件)。
链接器的主要任务为:
①符号解析:symbol resolution 目标文件中定义和引用符号。每个符号可能对应于一个函数、一个全局变量、或者一个静态变量(局部变量在运行时栈中创建) 符号解析就是将符号引用与符号定义关联起来。
②重定位:relocation 编译器和汇编器生成的地址是从0开始的逻辑地址。链接器通过把每个符号定义与内存中一个位置关联起来,从而重定位这些部分。具体做法是使用汇编器产生的重定位条目信息执行重定位。
目标文件存在三种类别:
- 可重定位目标文件:包含二进制的代码和数据,可以在编译时与其它可重定位目标文件组合起来,生成可执行目标文件。
- 可执行目标文件:包含二进制代码和数据,其形式可直接复制进内存执行
- 共享目标文件:一种特殊的目标文件,可以在加载或者运行时被动态的加载进内存并链接。
【目标文件格式】
目标文件是对程序二进制代码和数据的组织,贝尔实验室第一个Unix系统使用a.out格式,windows使用可移植可执行的PE格式(如最常见的可执行程序.exe格式) 而Linux中使用的是ELF (executable and linkable format)格式。

ELF头:描述系统的字的大小以及字节顺序(大小端) 以及其它用于链接器语法分析和解释目标文件的信息。
目标文件通常包含以下这些节:
.text | 程序的机器代码 |
.rodata | read only data 只读数据,如printf 中格式串 |
.data | 已经初始化的全局或静态变量(局部变量运行期创建在栈中) |
.bss | 未初始化的全局或静态变量,不占据实际空间,仅仅为一个占位符 |
.symtab | 符号表:存放程序中定义和引用的函数和全局(静态)变量信息 |
.rel.text | 对应与.text中位置的列表,调用外部函数或者引用全局变量时修改 |
.rel.data | 被模块引用或者定义的所有全局变量的重定位信息 |
objdump -x add.o linux中采取objdump才看ELF目标文件信息
可以看到目标文件中,主要包含代码段、数据段等节,同时以及最重要的符号表(symbol_tab)
自定义函数 add_funii (已经被编译期重新修改函数名 典型的加上i i 对应参数描述) 在符号表最后一个。
【符号与符号表】
符号表包含所在可重定位模块定义和引用的符号的信息,通常包含三种不同的符号:
类别 | 对应于源程序 |
---|---|
全局符号:由本模块定义,能被其它模块引用的符号 | 对应于C++源代码中定义的全局变量和函数 |
局部符号:由本模块定义,但不能被其它模块引用的符号 | 对应于C++源代码中定义的本文件可见的static 变量和函数 |
外部符号:由其它模块定义并被本模块引用的全局符号 | 本文件声明使用,在其它源文件定义的全局变量和函数 |
注意:
符号表中不包含任何局部非静态变量的任何符号,这是因为这些局部变量都是在运行时栈中创建和销毁。
因此,static关键字最重要的用途:使得被修饰的函数或变量只能在本模块(文件)中可见。