一、什么是符号?
每个可重定位目标模块m都有一个符号表,它包含m定义和应用的符号的消息。那么什么是符号呢?
简单来讲,符号氛围三种:
1、全局符号:没有用static修饰的函数和全局变量
2、局部符号:用了static修饰的函数和全局变量
3、外部符号:在模块A中定义,并被模块B引用的全局符号
二、符号定义和符号引用
以这两个程序为例,辨别哪些是符号的定义,哪些是符号的引用。
三、符号与符号表
符号表存放着程序中所有符号的信息,我们以swap程序为例,查看符号表中的信息
Type代表种类,FUNC函数,OBJECT对象,也有NOTYPE对应未定义类型
Ndx:COM代表未初始化的数据(.bss),UND表示未定义,1则是放入.text节,详细请看可重定位文件格式。
·Value代表在节中的偏移量
接着以上面的两个程序为例,辨别符号类型
符号 | .symtab条目 | 符号类型 | 在哪个模块中定义 | 节 |
buf | 是 | 外部符号 | main.c | .data |
bufp0 | 是 | 全局符号 | swap.c | .data |
bufp1 | 是 | 全局符号 | swap.c | .common |
swap | 是 | 全局函数 | swap.c | .text |
temp | 否 | 非符号 | swap.c | 栈 |
四、符号解析
链接器解析符号引用的方法是将每个引用与他输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来,通俗来讲就是程序中的变量必须能与符号表中唯一对应。
编译器只允许每个模块中每个局部符号只有一个定义,所以符号解析简单明了。
但是对全局符号的引用解析就很麻烦。当编译器遇到一个不是在当前模块中定义的符号时,会把他交给链接器处理,在其他文件中寻找该符号,如果找不到则报错。
问题在于不同程序之间可能会定义名字相同的全局符号,链接器必须选择其中一个,抛弃其他定义,那么在Linux系统下则存在着有全局符号的规则
五、链接器解析多重定义的全局符号
首先我们先把全局符号区分为强符号和弱符号
强符号:函数和已初始化的全局变量
弱符号:未初始化的全局变量
当链接器遇见多个名字相同的全局符号是,取舍便遵循以下三条规则
1、不允许有多个同名的强符号
2、如果有一个强符号和多个弱符号同名,那么选择强符号
3、如果有多个弱符号同名,那么从这些弱符号中任意选择一个
①不允许有多个同名的强符号
x有两次强定义,p1有一次弱定义一次强定义、main有一次强定义。链接结果
②
按理来说,结果应该是d=0,x=200,课时我们来运行一下
d的值是正确的,但是x的值却非常的奇怪,甚至在链接后出现了warning,我们来研究一下为什么。
首先这涉及到整数和小数在计算机中的存储方式。
100和200在计算机中分别占四个字节,但是当遇到d=1.0是,链接器选择了d=100的这个强定义d,将二者的地址归为一个,于是double d的八个字节覆盖了int d和int x
1.0在计算机中的存储样式为 3f f0 00 00 00 00 00 00(详细请看小数在计算机中存储方式),小端法
所以x读出来的数据是0x3ff00000,即1072693248