LV03-01-链接库-01-基本概念
本文主要是C语言——链接库基本概念的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 | Windows | Windows11 |
Ubuntu | Ubuntu20.04.6的64位版本 | |
VMware® Workstation 17 Pro | 17.0.0 build-20800274 | |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) | |
Win32DiskImager | Win32DiskImager v1.0 | |
Linux开发板环境 | Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) | |
linux内核 | linux-4.15(NXP官方提供) |
点击查看本文参考资料
- 通用
参考资料 | 相关链接 |
--- | --- |
点击查看相关文件下载
--- | --- |
一、链接库
1. 什么是链接库?
计算机中,有些文件专门用于存储可以重复使用的代码块,例如功能实用的函数或者类,这些通常被称为库文件,简称库( Library )。
链接库就是将开源的库文件进行编译、打包操作后得到的二进制文件。包含的代码可被程序调用,常见的有标准 C 库、数学库、线程库……等等。虽然链接库是二进制文件,但无法独立运行,必须等待其它程序调用,才会被载入内存。
在 linux 下,库文件大多存在于 /usr/lib 目录和 /lib 目录下,另外要注意的就是 Windows 和 Linux 下库文件的格式不兼容。
2. 两种库
库有两种:静态库(.a、.lib)和动态库(.so、.dll)。所谓静态、动态是指链接。后面再详细学习链接。
2.1 静态库
静态库(Static Library)是一种包含多个目标文件(.o文件)的归档文件,通常具有.a或.lib的扩展名。静态库在编译时与程序一起链接,成为程序二进制文件的一部分。因此,使用静态库编译的程序在运行时不需要额外的动态链接库支持。
- 静态库的优点:
(1)简单易用:静态库与程序一起编译链接,无需考虑运行时依赖问题。
(2)兼容性好:由于静态库与程序一起编译成单个二进制文件,因此不存在版本兼容性问题。
(3)性能优化:编译器可以进行更多的优化,如内联函数扩展、常量折叠等。
- 静态库的缺点:
(1)占用空间:静态库的内容会全部嵌入到最终的程序中,导致程序体积增大。
(2)更新困难:如果需要更新静态库中的某个函数,必须重新编译链接整个程序。
(3)资源浪费:如果多个程序使用了同一个静态库,那么每个程序中都会包含一份静态库的代码,造成内存和存储空间的浪费。
2.2 动态库
动态库(Dynamic Library)是一种在程序运行时加载的库,它包含了可被多个程序共享的代码和数据。动态库在Windows系统中通常具有.dll(Dynamic Link Library)的扩展名,在类Unix系统中则通常具有.so(Shared Object)的扩展名。与静态库不同,动态库在编译时不会被完全链接到程序中,而是在程序运行时动态加载。
- 动态库的优点:
(1)节省空间:多个程序可以同时使用同一个动态库,减少了存储空间和内存占用。
(2)易于更新:动态库可以在不重新编译链接整个程序的情况下进行更新。
(3)插件支持:动态库可以作为插件来扩展程序的功能。
- 动态库的缺点:
(1)运行时依赖:程序在运行时需要找到并加载动态库,否则无法正常运行。这可能导致“DLL Hell”问题,即因为动态库版本或路径问题导致的程序崩溃或错误。
(2)兼容性问题:不同版本的动态库可能不兼容,需要确保程序与动态库的版本匹配。
(3)性能开销:动态库的加载和链接过程可能带来一定的性能开销。
2.3 静态库与动态库的比较
(1)链接时间:静态库在编译时与程序一起链接,而动态库在运行时动态加载。
(2)空间占用:静态库会增加程序的体积,而动态库可以实现多个程序之间的代码共享,节省空间。
(3)更新难度:静态库更新需要重新编译链接整个程序,而动态库可以在不修改程序的情况下进行更新。
(4)兼容性:静态库与程序一起编译成单个二进制文件,不存在版本兼容性问题;而动态库可能因版本不匹配导致兼容性问题。
(5)性能:静态库在编译时可以进行更多的优化,而动态库在运行时加载链接可能带来一定的性能开销。但在实际应用中,这种性能差异通常可以忽略不计。
二、两种链接方式
上面都是一些基本概念,接下来我们结合C语言的编译过程来深入了解一下动态库和静态库。
1. 回顾一下
我们来回顾一下程序编译成可执行程序的过程。
这一部分的知识重点与链接器相关,每个 C 语言源文件被编译后生成目标文件,这些目标文件最终要被链接在一起生成可执行文件。 链接器的主要作用是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确的衔接。
2. 两种链接方式
我们知道一个完整的 C 语言项目可能包含多个 .c 源文件,项目的运行需要经过“编译”和“链接”两个过程:
- 编译:由编译器逐个对源文件做词法分析、语法分析、语义分析等操作,最终生成多个目标文件。每个目标文件都是二进制文件,但由于它们会相互调用对方的函数或变量,还可能会调用某些链接库文件中的函数或变量,编译器无法跨文件找到它们确切的存储地址,所以这些目标文件无法单独执行。
- 链接:对于各个目标文件中缺失的函数和变量的存储地址(后续简称“缺失的地址”),由链接器负责修复,并最终将所有的目标文件和链接库组织成一个可执行文件。
一个目标文件中使用的函数或变量,可能定义在其他的目标文件中,也可能定义在某个链接库文件中。链接器完成完成链接工作的方式有两种:静态链接和动态链接。
静态链接的过程由静态链接器负责完成,动态链接的过程由动态链接器负责完成。链接器的实现机制和操作系统有关,例如 Linux 平台上,动态链接器本质就是一个动态链接库。
2.1 静态链接
无论 各个目标文件中缺失的函数和变量的存储地址 位于其它目标文件还是链接库,链接库都会逐个找到各目标文件中这些缺失的地址。采用此链接方式生成的可执行文件,可以独立载入内存运行,这种链接方式称为静态链接,用到的链接库称为静态链接库。
这种在生成可执行文件之前完成所有链接操作,所使用的静态链接库有如下特点:
- 编译(链接)时把静态库中相关代码复制到可执行文件中,这也意味着文件本身的体积会很大。
- 程序中已包含代码,运行时不再需要静态库。
- 程序运行时无需加载库,运行速度更快。
- 当系统中存在多个链接同一个静态库的可执行文件时,每个可执行文件中都存有一份静态库的指令和数据,这会占用更多磁盘和内存空间。
- 静态库升级后,整个程序就必须重新链接后才能运行,。假设一个程序有 10 个模块构成,每个模块的大小为 1 MB,那么每次更新任何一个模块,用户就必须重新获取 10 MB 的程序。
静态库与汇编生成的目标文件一起链接为可执行文件,生成的可执行文件可以独立运行。那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。
2.2 动态链接
链接器先从所有目标文件中找到部分缺失的地址,然后将所有目标文件组织成一个可执行文件。如此生成的可执行文件,仍缺失部分函数和变量的地址,待文件执行时,需连同所有的链接库文件一起载入内存,再由链接器完成剩余的地址修复工作,才能正常执行。
在这种链接方式中,链接所有目标文件的方法仍属静态链接,而载入内存后进行的链接操作称为动态链接,用到的链接库称为动态链接库。动态链接库是 Windows 平台上对动态链接过程所用库文件的称呼, Linux 平台上更习惯称为共享库或者共享对象文件。
总的来说动态链接,指的就是将链接的时机推迟到程序运行时再进行。具体来讲,对于一个以动态链接方式运行的项目,首先由静态链接器将所有的目标文件组织成一个可执行文件,但是在程序编译时并不会被连接到目标代码中,而会在运行时将所需的动态链接库全部载入内存,由动态链接器完成可执行文件和动态库文件的链接工作。动态链接库有如下特点:
编译(链接)时仅记录用到哪个共享库中的哪个符号,不复制共享库中相关代码,这样程序不包含库中代码,文件本身的体积就没那大了。
不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。
动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
库升级方便,无需重新编译程序。
使用更加广泛。
采用动态链接的方式,每次程序运行时都需要重新链接,这样确实会损失一部分程序性能,但实际情况是,动态链接库和静态链接相比,性能损失大约在 5% 以下,由此换取程序在空间上的节省以及更新时的便利,是相当值得的。
动态链接库可以随可执行文件一同载入内存,也可以在可执行文件运行过程中载入,即可执行文件什么时候需要,动态链接库才会载入内存。
三、命名规范
1. 静态库
Linux静态库命名规范,必须是”lib[your_library_name].a”:lib为前缀,中间是静态库名,扩展名为.a。
2. 动态库
Linux动态链接库的名字形式为 libxxx.so,前缀是lib,后缀名为”.so”。
- 针对于实际库文件,每个共享库都有个特殊的名字”soname”。在程序启动后,程序通过这个名字来告诉动态加载器该载入哪个共享库。
- 在文件系统中,soname仅是一个链接到实际动态库的链接。对于动态库而言,每个库实际上都有另一个名字给编译器来用。它是一个指向实际库镜像文件的链接文件(lib+soname+.so)。
四、两个命令
1. nm
1.1 使用格式
有时候可能需要查看一个库中到底有哪些函数,nm命令可以打印出库中的涉及到的所有符号。库既可以是静态的也可以是动态的。基本格式如下:
1 | NAME |
nm列出的符号有很多,常见的有三种:
1 | U 在库中被调用,但并没有在库中定义(表明需要其他库支持) |
1.2 使用实例
我们用后面编译的zlib库先看一眼:lib/zlib-1.2.10/lib · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
2. ldd
2.1 使用格式
ldd命令可以查看一个可执行程序依赖的共享库,基本命令格式如下:
1 | ldd [option]... file... |
2.2 使用实例
我们这里以ls命令为例(man手册也是这各实例):
1 | ldd /bin/ls |