链接库是什么

所谓链接库,从字面上理解,指的是程序在“链接”阶段使用的代码库。

“链接”是源程序转换成可执行程序必须经历的步骤之一,考虑到一些初学者对程序的运行过程还不了解,接下来先解释一下“链接”的含义,再系统地讲解链接库。

链接

很多编程语言写出的源代码,必须经过「编译」和「链接」这两个步骤,才能转换成可执行的程序,比如 C语言、C++、C# 等。

将源代码转换成计算机能够识别的二进制程序,这个过程叫做编译。

读者可以把编译理解成“翻译”,类似将中文翻译成英文、将英文翻译成象形文字。 编译是一个复杂的过程,大致包括词法分析、语法分析、语义分析、性能优化、生成二进制代码五个步骤,期间涉及到复杂的算法和硬件架构,感兴趣的读者请自行阅读《编译原理》一书,这里我们不再展开讲解。

编译源代码后生成的文件叫做目标文件(Object File),例如 Visual Studio 下的.obj,或者 GCC 下的.o,它们都是目标文件。注意,目标文件里存储的二进制代码还不完整,不能直接运行。

实际开发中软件的规模往往都很大,动辄数百万行代码,为了方便阅读和维护,程序员会把它们分散到多个文件里。每个文件存储的都是源代码片段,编译它们会生成多个目标文件,这些文件中存储的都是二进制代码片段,每个都不完整,所以不能直接运行。

即便将所有的源代码写在一个源文件里,编译生成一个目标文件,文件中的二进制代码还是不能运行,因为缺少运行所需要的系统组件(比如标准库、链接库等)。

所有目标文件里的二进制代码,以及运行所需要的系统组件(比如标准库、链接库等),把它们组合到一起的过程就叫做链接。链接完成后会生成一个可执行文件,里边存储的就是可执行程序。

你看,程序的链接阶段确实会用到链接库。

关于编译和链接的具体细节,不再深入讲解,感兴趣的读者可以阅读《编译和链接》模块。

链接库

链接库是库的一种,它存放的不是源代码,而是编译后生成的二进制代码。

链接库自己是没法运行的,必须等待别的程序在链接阶段调用它,然后它们一起组合成可执行程序。这样当可执行程序运行时,链接库的程序就能运行了。

链接库的组合方式有两种,分别是:
  1. 直接把链接库中的代码和数据拷贝一份,添加到可执行文件中。最终生成的可执行程序,包含要运行的所有代码,能够脱离链接库独自运行,这样的组合方法叫做静态链接;
  2. 先把所有目标文件组合成一个可执行文件,文件中缺少链接库的代码和数据,不能脱离链接库独立运行。换句话说,程序运行时必须和链接库一起载入到内存中,然后它们在内存中组合成完整的代码,才能正常运行,这样的组合方法叫做动态链接。

也就是说,静态链接是程序载入内存之前完成的,而动态链接是将链接的时机推迟到程序载入内存之后完成。采用静态链接方式的库叫做静态链接库,采用动态链接方式的库叫做动态链接库

动态链接库是 Windows 平台的叫法,Linux 平台上习惯叫做共享库或者共享对象文件,它们表达的是一个意思。

静态链接库

调用静态链接库生成的可执行程序,程序中包含链接库所有的代码和数据,所以它可以脱离链接库独自运行。

在 Windows 环境中,静态链接库的后缀名通常是.lib;在 Linux 环境中,静态链接库的后缀名通常是.a

调用静态链接库有很多好处,比如可执行程序能够在没有链接库的环境中运行,程序移植到其它的系统环境时不需要额外安装和配置链接库,链接库版本发生变化也不会对程序造成影响等。

静态链接库也存在一些弊端,比如:
  • 由于链接库的代码和数据复制到了可执行文件中,当程序功能较为复杂的时候,整个文件的体积会非常大,加载到内存中的时间就会比较长,最直接的一个例子就是双击打开一个软件,要很久才能看到界面,非常影响用户体验;
  • 当静态链接库需要更新时,所有调用此链接库的可执行程序都需要重新编译和链接,大大增加了维护的工作量;
  • 对于运行着的多个程序,如果它们调用的是同一个静态链接库,那么链接库的代码和数据在内存中就会出现很多份,造成了内存资源的浪费。

实际场景中,如果强调程序能够脱离链接库独自运行,避免不同的系统环境影响程序的正常运行,那么静态链接库是不错的选择;反之,如果链接库需要频繁更新、或者强调对内存资源的高效利用,静态链接库就不适合了。

动态链接库

调用动态链接库生成的可执行程序,程序中不包含链接库的代码和数据,所以程序无法脱离链接库独自运行。换句话说,程序要想执行,必须连同动态链接库一起载入到内存中。

在 Windows 环境下,动态链接库的后缀名通常是.dll;在 Linux 环境下,动态链接库的后缀名通常是.so

动态链接库载入内存的方式有两种,分别是:
  • 隐式加载:又叫载入时加载,指的是可执行程序载入内存时搜索动态链接库,并将链接库的代码和数据载入内存;
  • 显式加载:又叫运行时加载,指的是可执行程序在运行过程中,需要动态链接库里的代码和数据时再加载。

隐式加载也会有静态链接库的问题,如果程序稍大,加载时间就会过长,用户不能接受。显式加载则不存在这个问题,它是将较大的程序分开加载的,程序运行时只需要将可执行程序载入内存,后续需要再载入动态链接库,软件打开速度快,用户体验好。

关于隐式加载和显式加载动态链接库的具体实现,后续章节会做详细地讲解。

和静态链接库相比,动态链接库可以很好地解决空间浪费和更新困难的问题。

动态链接库和可执行文件是分开载入内存的,当有多个程序调用同一个动态链接库时,所有程序共享一份动态链接库的代码和数据,有效避免了内存资源的浪费。

当可执行程序调用的动态链接库需要更新或者升级时,直接用新的库文件把旧的替换掉,程序运行时会自动载入新的动态链接库。

有读者可能会问,采用动态链接的方式,程序每次运行都需要重新链接,会不会很慢?的确,动态链接确实会损失一部分程序性能,但实践证明,动态链接库和静态链接相比,性能损失大约在 5% 以下,由此换取程序在空间上的节省以及更新时的便利,是相当值得的。

总结

链接库指的就是程序在链接阶段使用的代码库。

程序载入内存之前完成链接工作的,称为静态链接库;程序载入内存以后完成链接工作的,称为动态链接库。

静态链接库和动态链接库各有优缺点,要根据实际的项目需求和约束条件选择合适的链接方式。

关  闭