继续更新《程序员的自我修养》这个系列,主要是夏天没把它看完,补上遗憾。本篇来自书中第七章。
再说动态链接前,我们先阐明为什么要动态链接:
动态链接的产生来自静态链接的局限性。随着静态链接的发展,其限制也越来越大,如浪费空间、模块更新困难等问题暴露了出来。使得人们不得不寻找其他方法,动态链接应运而生。
目录
7.1 动态链接基本介绍
7.2 简单的动态链接
7.3 地址无关代码
未完待续
7.1 动态链接基本介绍
如上图,静态链接中,progran1和program2都用到了Lib.o,他们分别将Lib.o加到代码内容里并在运行时内存中有两份Lib.o分别来自这两个程序。
对于程序的开发和发布,静态链接有两个缺点:1. 如果Lib.o来自其他开发团队,他们更新了其中的bug并修改重新发版了,我们作为使用方必须跟进发版,如果我们使用到类似Lib.o的其他lib呢,是不是每个lib发版我们都跟进呢?2. 如果我们使用到了20个公共库里的lib,静态链接将其都装在起来就是20M,这还不算我们自己开发的内容。
动态链接简介:
动态链接的思想是将程序分割开来,以上图为例:系统将program1装载到内存中,发现它依赖Lib.o,于是将Lib.o也装进去,如果还有什么别的依赖通通装载进去,这个过程和静态链接一样。不同的地方在将program2装载进去后,系统发现内存里已经有了一份Lib.so,那么程序只需将program2和内存里的Lib.o链接起来就好了。
对于程序的拓展性与兼容性来讲。动态链接思想就是插件思想,只有在使用时才装载到内存,可以使用插件思想做程序拓展。动态链接还加强程序的兼容性,如A操作系统和B操作系统对于printf的实现不同,只需操作系统A和B都能提供一个printf的动态链接库。
动态链接在操作系统的实际实现上更为复杂,主流操作系统都支持动态链接,在Linux中被称为动态共享对象(dynamic shared objects),一般以.so为结尾。windows中叫动态链接库(dynamic linking library),文件名以.dll结尾。本质上讲执行程序和动态链接程序本质上都是指令和数据,我们尽可以将其看作他们为平等的程序模块。
linux系统中C语言运行库为glibc,他的动态链接库在/lib下,名字为libc.so,当程序被装载时,系统的动态链接器会将程序所需要的动态链接库装载到地址空间,并将所有未决议的符号绑定到动态链接库中,最后进行重定位工作。这块可以理解为将运行程序中没有找到实现的部分去动态链接库里找,如果没找到,外部链接报错,在visual stadio中很常见了。
我们都知道不可能三角,在程序中也同样成立,动态链接也不是一味的好。首先,早期的动态链接由于协议不全、规范不统一导致互相不兼容。其次,动态链接相比静态链接,链接过程在程序装载的时候,程序每次装载前都要重新链接,对程序运行效率有影响。
7.2 简单的动态链接
简单的例子四段代码:program1.c、program2.c、lib.h、lib.c.
使用命令gcc -fPIC -shared -o Lib.so Lib.c生成Lib.so
再编译program1和program2:
gcc -o program1 program1.c ./Lib.so
gcc -o program2 program2.c ./Lib.so
过程图:
链接器在符号链接时找到foobar。
对比静态链接的虚拟地址映射到物理地址,动态链接更复杂。静态链接只将一个文件整体映射到物理内存中。
为了方便查看地址空间,修改代码:
查看虚拟地址空间分布:
用readIf来查看Lib.so的装载属性和普通执行文件没区别,但是它地址是从0x00000000开始的,实际不是从这,这意味着动态链接地址在编译时不确定,在装载时根据当前空间去分配。
7.3 地址无关代码
装载时重定位。
我们在静态链接时提到过链接重定位(link time relocation),现在为装载重定位(load time relocation),在windows 这种装载时重定位又叫做基址重制(Rebasing),linux的GCC支持这种装载时重制的方法,使用-shared那么输出对象就是使用装载时重定位方法。
地址无关代码
汇编分析:
- 模块内部调用跳转: