链接分为静态链接和动态链接,静态链接使得不同的源文件可以互相调用,形成模块化,而动态链接则通过加载/运行时链接解决了共享库在不同应用中复制多份的浪费问题。
静态链接解决了不同程序之间模块化的问题。此外,修改一个源文件,不必重新编译整个应用程序,只需要编译修改的部分,再次链接即可。
例子:程序A.cpp可以调用B.cpp中的函数或者全局变量,下面通过一个简单的例子展示了从两个源文件到可执行的二进制文件的过程
以main.c为例,编译通常:
目标文件分为三类:
编译器和汇编器生成可重定位的目标文件,而链接器生成可执行的目标文件。
可重定位的目标文件由以下几个部分组成:
下面给出各个部分的注解:
名称 | 英文全称 | 注解 |
---|---|---|
.text | text | 已编译程序的机器代码 |
.rodata | read only data | 只读数据,比如printf的格式化字符串,switch语句的跳转表 |
.data | data | 已初始化的全局/静态C变量 |
.bss | block storage start | 未初始化的全局/静态C变量 |
.symtab | symbol table | 符号表,存放定义和引用的 函数/变量 信息 |
.rel.text | relocated text | text节中需要做重定位修改的指令位置的列表 |
.rel.data | relocated data | 被 引用/定义 的所有全局变量的重定位信息 |
静态链接程序通常以一组可重定位的目标文件(.o文件)为输入,输出一个可执行的目标文件。
静态链接通常分为两个步骤:
符号解析通常是对变量命函数名的解析,将符号引用和其定义关联起来。
而重定位则是将代码中涉及内存位置的操作,比如 jmp (pc+偏移)跳转语句,在 .o 文件中,这些位置通常都是相对每个 .o 的起始位置,而在合并 .o 之后,位置发生改变,需要重新定位。
链接器符号分为三类:
符号解析的重点在于解析同名符号,同名符号又通过强弱来区分:
链接器通过如下的规则进行同名符号解析:
更加详细的版本【符号引用重定位 重定位PC相对引用 简单讲解】
符号解析完成之后,需要进行重定位。因为将不同的 .o 合并,而每个 .o 中的符号位置都是相对该 .o 而言的,所以需要进行重定位。
重定位分为三个步骤:
重定位相当简单。假设main.c调用sum.c,以call指令(函数调用)PC+偏移的重定位为例,其实就是计算一下两者之间地址的差值即可,注意加上 r.addend 以补偿call指令的PC:
老式的静态链接解决方案,将所有 .o 文件打包为一个单独的库文件即 .a 文件(archive,存档文件)
如图:编译 atoi, printf, random函数等等,成libc.a共享库
在构建可执行文件时,只需指定库文件名,然后链接器自动去对应的库中找用到的模块,并且只拷贝用到的模块。
链接到静态库时,通过如下的机制对外部引用进行解析:
如果扫描到最后,U中还有未知符号,则报错。
和静态库链接,也要按照基本法 引用解析机制,因为命令行给出的顺序不同,很可能导致不同的结果:
比如以下的互相调用的情况
则应该使用如下的命令行(即在最后补上一个x,解析U中的y的符号,从而形成闭环)
静态库及其静态链接有很多缺点,比如:
所以出现动态链接及共享库的概念:
共享库的优点有:
以下哪个工作不属于动态链接器完成的 ?
A、完成对引用的动态库函数在进程空间的布局。
B、完成动态库函数的编译和汇编。
C、完成可执行文件对动态库函数引用的符号解释。
D、完成可执行文件对动态库函数引用的地址确定。
答:编译和汇编是编译阶段的工作,不是链接阶段的,所以选B
ELF格式可用来保存哪些类型的目标文件?
答:可重定位的目标文件、可执行的目标文件、共享目标文件
答:
m1中int x
是强符号,m2中float x
是弱符号,所以绑定到强符号,A正确,B错误。
p1函数被分配到内存中代码区域,p1函数中的临时变量 p1 被分配到栈中,C正确
x,main和p1都不违反强符号只能出现一次的法则,故合法,D正确
假设调用关系如下:hello.c调用libx.a和liby.a中的函数,liby.a调用了libz.a的函数,libz.a调动了libx.a和liby.a中的函数,以下编译正确的是 。
A、gcc –o hello hello.c ./libx.a ./liby.a ./libz.a ./libx.a
B、gcc –o hello ./libx.a ./liby.a ./libz.a ./libx.a ./liby.a hello.c
C、gcc –o hello hello.c ./libx.a ./liby.a ./libz.a ./libx.a ./liby.a
D、gcc –o hello hello.c ./libx.a ./liby.a ./libz.a
答:由库链接时的符号解析机制(即U未知符号表的读写)可知,在最后解析 libz.a之后,会存在 libx, liby的符号未被解析,因此需再次添加 libx, liby,所以选 C
可重定位目标文件中机器代码、只读数据、未初始化的全局变量以及已初始化数据通常分别保存在哪些节里?
答:
机器代码:.text
只读数据:.rodata
未初始化的全局变量:.bss
初始化数据:.data