这篇文章记录静态链接和动态链接以及静态库和动态库的原理。
1 静态链接和动态链接
链接其实就是连接的意思,将所有相关的东西连接起来。
1.1 静态链接
什么是静态链接?编译时候的链接就是静态链接,所以ld/collect2链接程序,也称为静态链接器。在编译时候,调用gcc自动调用ld/collect2链接程序,将所有的.o文件整合在一起,然后保存在可执行文件中。
静态链接做了两件事情,分别是符号解析和重定位。
1.1.1 符号解析
符号解析的目的就是将每个X.c生成的目标文件X.o中的引用和符号的定义联系起来。
1.1.2 重定位
重定位做的事情:将.o文件中每个机器指令的逻辑地址,重定位为实际运行的地址(虚拟内存地址)。如果是裸机运行的:运行的地址就是内存的物理地址;如果是基于OS运行的:运行地址就是虚拟内存的地址。不过虚拟内存机制,最终还是会将虚拟地址会转为物理地址。
上图中,每个段开始都是逻辑地址,从0开始的一个编号,这个编号只是一个编号,既不是虚拟内存地址也不是物理地址,就是一个标识。
通过collect2链接后,就可以将这些段的编号重定位成虚拟内存地址,如下图所示:
1.2 动态链接
动态链接,就是在编译时候只留下调用接口(函数所在库的相对地址),当程序真正运行时候,才去链接执行。动态链接不是在编译时发生的,而是在程序动态运行时发生的,所以叫称为动态链接。比如,printf,这个函数就是动态库提供的,程序编译后在调用printf处,只留下动态库的printf接口,程序运行起来后再去调用Printf函数。
下面说一下,什么是静态库和动态库?
2 静态库和动态库
这两个文件都是由许多.o文件打包成的文件,相当于一个文件仓库。
2.1 静态库
链接静态库时,就是将库中.o的代码包含到自己的程序中,每个程序链接静态库后,都会包含一份独立的代码,当这些程序都运行起来时,所有这些重复的代码都需要占独立的存储空间,显然很浪费计算机资源。
2.2 动态库
动态库就是为了解决静态库的缺点。
在链接动态库时,collect2/ld不会将动态库中.o的代码直接静态链接(复制)到自己程序中,只会留下调用接口。程序运行时再去将动态库(链接)加载到内存中,然后就能调用动态库的函数(代码)了。不管多少程序使用了这个动态库,这些程序只会共享使用同一份的动态库(只加载一份在内存中),因此动态库也被称为共享库。
动态库的实现原理
程序运行起来后,“动态链接器”一看想链接的是libc.so动态库,首先检查内存中有没有这个动态库。如果没有:到硬盘上找到libc.so库,将所有代码加载(动态链接)到内存中,并得到整个动态库在内存中的起始地址;如果有,说明之前已经加载过了,所以不再加载,直接得到动态库在内存中的起始地址即可。
我们调用动态库的printf时候,如何找到这个函数?
Printf编译后,程序中留下printf 在动态库中的相对地址。
Printf的绝对地址= printf的相对地址 + 动态库加载时候的地址
当动态链接器加载动态库之后,会得到动态库在内存中的起始地址,通过上边的计算公式,就得到了printf的真正地址。