1、符号解析
链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来,即使用的符号一定要找到相应的定义。可分为局部符号解析和全局符号解析。
局部符号解析:引用定义在相同模块中的局部符号的引用,符号解析非常的简单明了,就不用介绍了。
全局符号解析:当编译器遇到一个不是当前模块中定义的符号时,会假设该符号时在其他某个模块中定义的,生成一个连接器符号表条目,并把它交给链接器处理。如果链接器在它的任何输入模块中都找不到这个符号的定义,就输出一条错误信息并终止。
全局符号解析还因为多个目标文件可能会定义相同的名字的全局符号。在这种情况下,链接器必须要么标志一个错误,要么以某种方法选出一个定义并抛弃其它定义。
1.1、解析多重定义的全局符号
链接器的输入是一组可重定位目标模块。每个模块定义一组符号,有些是局部的(只对定义该符号的模块可见),有些是全局的(对其它模块也可见)。如果多个模块定义同名的全局符号,连接器会进行筛选。
在编译时,编译器向汇编输出每个全局符号,或者是强或者是弱,而汇编器把这个信息隐含地编码在可重定位目标文件的符号表中。函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。
根据强弱符号的定义,Linux链接器使用下面的规则来处理多重定义的符号名:
- 不允许有多个同名的强符号
- 如果有一个强符号和多个弱符号同名,那么选择强符号
- 如果有多个弱符号同名,那么从这些弱符号中任意选择一个
规则2和规则3的应用会造成一些不易察觉的运行时错误,对于不警觉的程序员来说,是很难理解的,尤其是如果重复的符号定义还有不同的类型时。如下,x在一个模块中为int,而在另一个模块中为double:
foo.c 文件中
void f(void);
int y = 15212;
int x = 15213;
int main()
{
f();
printf("x=0x%x y = 0x%x \n", x,y);
return 0;
}
bar.c 文件中
double x;
void f()
{
x = -0.0;
}
在64位系统上,double类型是8个字节&#x