链接的基本概念
链接是将各种代码和数据片段收集起来组合成一个单一文件的过程,且这个文件可以被加载到内存中并执行
- 链接可以执行于编译时,也就是在源代码被翻译成机器代码的过程中
- 链接可以执行于加载时,也就是在程序被加载器加载到内存中并执行
- 链接可以执行于运行时,也就是由应用程序来执行
在早期的计算机系统中,链接是手动执行的。在现代系统中,链接是由叫做链接器的长须自动执行的。
链接器
链接器在软件开发中扮演着一个关键的角色,因为它们使得分离编译成为可能。现在我们可以把源文件分成很小的文件,方便更好的管理。
链接器的作用是把多个可重定位目标文件组合起来,创建一个可执行文件。
链接器的主要任务:
- 符号解析:比如两个可重定位文件中有两个定义为a的变量,我们将它组合时则需变成只有一个a。
- 重定位:通过每个符号定义与一个内存位置关联起来。
可重定位目标文件
可重定位文件就是源文件进过编译器和汇编器之后的文件,可重定位文件有固定的格式:
- ELF头:ELF头以一个16字节的序列开始,这个序列描述了生成改文件的系统的字的大小和字节顺序。
- .text:已编译程序的机器代码。
- .rodata:只读数据。
- .data:已初始化的全局和静态C变量。
- .bss:未初始化的全局和静态C变量。
- .symtab:一个符号表,它存放着程序中定义和引用的函数和全局变量的信息。
- .rel.text:链接器调用时需要修改的地方。
- .rel.data:被模块引用或定义的所有全局变量的重定位信息。
- .debug:一个调用符号表。
- .line:原始C源程序中的行号和.text节中机器指令之间的映射。
- .strtab:一个字符串表。
链接器如何解析多重定义的全局符号
链接器的输入是一组可重定位目标模块。每个模块定义一组符号,有些是局部的,有些是全局的。如果多个模块定义同名的全局符号,会发生什么呢?
在编译时,编译器想汇编器输出每个全局符号,或者是强或者是弱,而汇编器把这个信息隐含的编码在可重定位目标文件的符号表里。函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。如果出现符号重复的现象则按照以下规则执行:
- 多个同名强符号则会出现错误。
- 一个强符号和多个弱符号则选择强符号。
- 如果有多个弱符号同名,则从弱符号中任选一个。
比如说:
/*fool.c/
int main()
{
return 0;
}
/*bar1.c/
int main()
{
return 0;
}
我们输入指令gcc fool.c bar1.c
看看得到什么结果:
正如规则1所描述的,两个强符号出现了问题,规则2,3不做展示。
这就是有关链接的部分内容。