首页 > 编程知识 正文

什么是静态链接和动态链接,静态连接库与动态连接库区别

时间:2023-05-06 09:14:18 阅读:178057 作者:2345

本文摘自程序员的自我修养链接路和库7.1节,这一节写得很好,直接拿来收藏

33558 www.wq 3028.top/technology/compile/20180727124 /

静态链接允许不同的程序开发人员和部门相对独立地开发和测试自己的程序模块,在某种意义上大大促进了程序开发的效率,也扩大了原本受到限制的程序的规模。 但是,慢慢地,静态链接的许多缺点也变得明显了。 例如,由于内存和磁盘空间浪费、模块更新困难等问题,人们不得不寻找更好的组织程序的模块。

内存和磁盘空间

静态链接这种方法确实简单,原理简单,在实践中很难实现,在操作系统和硬件不发达的初期,大多数系统都采用这种方式。 随着计算机软件的发展,这种方法的缺点很快就暴露出来了。 那就是静态连接方式非常严重地浪费计算机的内存和磁盘空间。 静态链接会严重浪费内存空间,尤其是在多进程操作系统中。 想象一下,除了printf (函数、scanf )函数、strlen (函数)等公共库函数之外,每个程序内部还保留着相当数量的其他库函数及其所需的辅助数据结构。 在当前的Linux系统中,普通程序使用的c语言静态库至少在1 MB以上。 那么,如果在我们的机器上运行100个这样的程序,就会浪费将近100 MB的内存。 如果磁盘上有2,000个这样的程序,就会浪费将近2 GB的磁盘空间。 在许多Linux计算机上,/usr/ddbd下有数千个可执行文件。

例如,图7-1所示的Program1和Program2分别包括Program1.o和Program2.o两个模块,它们还共享Lib.o这两个模块。 对于静态连接,Program1和Program2都使用名为Lib.o的模块,因此链接输出的可执行文件Program1和Program2同时有两个副本。 同时运行Program1和Program2时,Lib.o在磁盘和内存上都有两个副本。 如果系统中存在大量由多个程序共享的目标文件(如Lib.o ),则大多数文件都是无用的。 在静态链接中,c语言静态库是典型的浪费空间示例,如果数千个其他库需要静态链接,则空间浪费是不可想象的。

图7-1静态链接时复制内存中的文件

程序的开发和公开

空间的浪费是静态链接的问题之一,另一个问题是静态链接也给程序的更新、导入、发布带来很多麻烦。 例如,程序Program1中使用的Lib.o是由第三方制造商提供的,当该制造商更新Lib.o时(例如修改Lib.o中包含的bug时),Program1的制造商是最新版的Lib.o 需要获得新的Program1.o。这样做的缺点很明显,当程序中的模块更新时,整个程序将被重新链接并向用户公开。 例如,如果一个程序包含20个模块,每个模块都为1 MB,则每次更新其中一个模块时,用户都必须重新获取这个20 MB的程序。 如果程序使用静态链接,则通过网络更新程序非常不方便,因为如果程序中有任何小的更改,整个程序都会被重新下载。

动态链接

解决空间浪费和更新困难问题的最简单方法是将程序模块彼此拆分,并创建独立的文件,而不是静态链接。 简单地说,不进行与构成程序的目标文件的链接,在程序执行之前不进行链接。 也就是说,动态链接(Dynamic Linking )的基本思想是在运行时推迟链接这一进程。

以Program1和Program2为例,假设您有三个目标文件: Program1.o、Program2.o和Lib.o。 当运行名为Program1的程序时,系统首先装载Program1.o。 当系统发现Program1.o使用Lib.o时,也就是说,如果Program1.o依赖于Lib.o,则系统将加载下一个Lib.o。 如果Program1.o或Lib.o仍在使用。加载所有必需的目标文件后,如果满足依赖关系,则所有依赖关系的目标文件都将存在于磁盘上,并且系统将开始链接。 该链路行为的原理与静态链路非常相似,包括符号分析、地址重新定位等。 关于这一点,我之前已经详细说明了。 完成这些步骤后,系统开始将控制权移交给Program1.o中的程序入口,然后程序开始运行。 此时,如果需要运行Program2,则系统只需加载Program2.o,而无需重新加载Lib.o。 这是因为内存中已经存在Lib.o的副本(请参见图7-2 )。 系统只需链接Program2.o和Lib.o即可。 显然,上述方法解决了多个共享目标文件副本占用磁盘和内存空间的问题。 可以看到磁盘和内存中只存在一个Lib.o,而不是两个。 此外,在内存中共享目标文件

图7-2动态链接时复制内存中的文件

模块的优点不仅是节约内存,还可以减少物理页面的交换,不同进程之间的数据和指令访问集中在同一个共享模块上,从而提高CPU缓存的命中率。 上述动态链接方式还可以使程序更容易升级。 升级库或程序共享的模块时,理论上只需简单地复盖旧的目标文件即可,而无需重新链接所有程序。 下次运行程序时,新版本的目标文件将自动加载到内存中

链接起来,程序就完成了升级的目标。当一个程序产品的规模很大的时候,往往会分割成多个子系统及多个模块,每个模块都由独立的小组开发,甚至会使用不同的编程语言。动态链接的方式使得开发过程中各个模块更加独立,耦合度更小,便于不同的开发者和开发组织之间独立进行开发和测试。

程序可扩展性和兼容性
动态链接还有一个特点就是程序在运行时可以动态地选择加载各种程序模块,这个优点就是后来被人们用来制作程序的插件(Plug-in)。比如某个公司开发完成了某个产品,它按照一定的规则制定好程序的接口,其他公司或开发者可以按照这种接口来编写符合要求的动态链接文件。该产品程序可以动态地载入各种由第三方开发的模块,在程序运行时动态地链接,实现程序功能的扩展。动态链接还可以加强程序的兼容性。一个程序在不同的平台运行时可以动态地链接到由操作系统提供的动态链接库,这些动态链接库相当于在程序和操作系统之间增加了一个中间层,从而消除了程序对不同平台之间依赖的差异性。比如操作系统A和操作系统B对于printf()的实现制不同,如果我们的程序是静态链接的,那么程序需要分别链接成能够在A运行和在B运行的两个版本并且分开发布;但是如果是动态链接,只要操作系统A和操作系统B都能提供一个动态链接库包含printf(),并且这个printf()使用相同的接口,那么程序只需要有一个版本,就可以在两个操作系统上运行,动态地选择相应的printf()的实现版本。当然这只是理论上的可能性,实际上还存在不少问题,我们会在后面继续探讨关于动态链接模块之间兼容性的问题。

从上面的描述来看,动态链接是不是一种“万能膏药”,包治百病呢?很遗憾,动态链接也有诸多的问题及令人烦恼和费解的地方。很常见的一个问题是,当程序所依赖的某个模块更新后,由于新的模块与旧的模块之间接口不兼容,导致了原有的程序无法运行。这个问题在早期的Windows版本中尤为严重,因为它们缺少一种有效的共享库版本管理机制,使得用户经常出现新程序安装完之后,其他某个程序无法正常工作的现象,这个问题也经常被称为“DLLHell”。

动态链接的基本实现
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有的程序模块都链接成一个个单独的可执行文件。那么我们能不能按照前面例子中所描述的那样,直接使用目标文件进行动态链接呢?这个问题的答案是:理论上是可行的,但实际上动态链接的实现方案与直接使用目标文件稍有差别。我们将在后面分析目标文件和动态链接文件的区别。动态链接涉及运行时的链接及多个文件的装载,必需要有操作系统的支持,因为动态链接的情况下,进程的虚拟地址空间的分布会比静态链接情况下更为复杂,还有一些存储管理、内存共享、进程线程等机制在动态链接下也会有一些微妙的变化。目前主流的操作系统几乎都支持动态链接这种方式,在Linux系统中,ELF动态链接文件被称为动态共享对象(DSO,Dynamic Shared Objects),简称共享对象,它们一般都是以“.so”为扩展名的一些文件;而在Windows系统中,动态链接文件被称为动态链接库(Dynamical Linking Library),它们通常就是我们平时很常见的以“.dll”为扩展名的文件。

从本质上讲,普通可执行程序和动态链接库中都包含指令和数据,这一点没有区别。在使用动态链接库的情况下,程序本身被分为了程序主要模块(Program1)和动态链接库(Lib.so),但实际上它们都可以看作是整个程序的一个模块,所以当我们提到程序模块时可以指程序主模块也可以指动态链接库。在Linux中,常用的C语言库的运行库glibc,它的动态链接形式的版本保存在“/lib”目录下,文件名叫做“libc.so”。整个系统只保留一份C语言库的动态链接文件“libc.so”,而所有的C语言编写的、动态链接的程序都可以在运行时使用它。当程序被装载的时候,系统的动态链接器会将程序所需要的所有动态链接库(最基本的就是libc.so)装载到进程的地址空间,并且将程序中所有未决议的符号绑定到相应的动态链接库中,并进行重定位工作。程序与libc.so之间真正的链接工作是由动态链接器完成的,而不是由我们前面看到过的静态链接器ld完成的。也就是说,动态链接是把链接这个过程从本来的程序装载前被推迟到了装载的时候。可能有人会问,这样的做法的确很灵活,但是程序每次被装载时都要进行重新进行链接,是不是很慢?的确,动态链接会导致程序在性能的一些损失,但是对动态链接的链接过程可以进行优化,比如我们后面要介绍的延迟绑定(Lazy Binding)等方法,可以使得动态链接的性能损失尽可能地减小。据估算,动态链接与静态链接相比,性能损失大约在5%以下。当然经过实践的证明,这点性能损失用来换取程序在空间上的节省和程序构建和升级时的灵活性,是相当值得的。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。