链接器是如何实现不同模块间数据交换和功能调用的工作原理?
摘要:链接器解析符号 	链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来,可重定位目标文件的符号表在随笔ELF可重定位目标文件 - mjy66 - 博客园 (cnblogs.c
链接器解析符号
链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来,可重定位目标文件的符号表在随笔ELF可重定位目标文件 - mjy66 - 博客园 (cnblogs.com)中有提到,以ELF格式的目标文件举例,.symtab节就是其符号表。
在解析符号的过程中,编译器针对局部符号和全局符号有不同的规则。在解析局部符号的过程中,编译器只允许每个模块中每个局部符号有一个定义,而对于全局符号的解析,若遇到一个不是在当前模块中定义的符号,编译器会假设该符号是在其它模块中定义,生成链接器符号表条目,若后续链接器在任何输入模块中都找不到被引用的符号定义,则会报错。
1、链接器如何解析多重定义的全局符号
链接器的输入是一组可重定位目标模块,每个模块有定义自己的一组符号,有些是局部(只对定义该符号的模块可见),有些是全局的(对其他模块也可见)。若多个目标模块定义了同名的全局符号,在Linux系统中,汇编器会以强符号或者弱符号来标记每个全局符号,函数和已初始化的全局变量为强符号,未初始化的全局变量为弱符号。Linux会根据以下规则处理多重定义的符号名:
规则1:不允许有多个同名的强符号
规则2:如果有一个强符号和多个弱符号同名,则选择强符号
规则3:如果有多个弱符号同名,则从这些弱符号中任意选择一个。
例1:
//main.c
int x = 1000;
int main()
{
return 0;
}
//f.c
int x = 1000;
void f()
{
}
main.c文件中定义并初始化了一个全局变量x,f.c文件中也定义并初始化了一个全局变量x,将这两个文件放在一起编译,会违反第一条规则,出现了两个同名的强符号,因此会出现如下报错。
例2:
//main.c
int x = 1000;
int main()
{
printf("x = %d\n",x);
return 0;
}
//f.c
int x;
void f()
{
}
若不对f.c文件中x进行初始化,则f.c中的全局变量x变成了弱符号,此时根据规则2,会优先选择main.c文件中的强符号,因此编译之后,运行就会出现如下结果:
2、静态库的链接
假设链接器不是读取一组可重定位目标文件,而是将所有相关的目标模块打包成一个单独的文件再作为链接器的输入,当链接器构造一个输出的可执行文件的时候,它只复制这个单独文件里被应用程序引用的目标模块,这个单独的文件就是静态库。静态库的出现能够在节省计算机内存的情况下,方便程序员调用相关函数。
在linux系统中,静态库以存档的特殊文件格式存放在磁盘中,存档文件是一组连接起来的可重定位目标文件的集合,有一个头部用来描述每个成员目标文件的大小和位置,存档文件名用后缀.a标识。
实践能够让我们对知识点的理解更加深刻,接下来将使用AR工具创建一个自己简单的静态库。创建静态库的步骤如下:
1、编写源文件
我们先创建三个分别对向量进行加减乘操作的文件,并在每个文件中记录被调用的次数。
