静态库和动态库的认识和使用
- 静态库和动态库的概述
- 动静态库的实现
- 静态库
- 动态库
- 库文件名称和引入库的名称
静态库和动态库的概述
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文
件的整个机器码
在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
也就是对于静态库来说,程序会在链接阶段将库函数的代码毫无保留的链接到可执行文件中,这就使得我们可以看到静态库的底层代码细节,对于静态库提供者,这带来一些不安全的问题,同时对于静态库的使用者,会显得代码臃肿。
对于动态库来说,动态库是在程序运行时才链接的,当程序需要调用某个库文件中的函数时,操作系统会在运行时自动加载该库文件,并将其中的函数(也就是返回函数的地址)链接到程序中,动态库的底层代码细节并不对外展示,从而实现代码的共享和复用。这样,多个程序可以共享同一个库文件,从而大大减少了程序的磁盘占用空间,同时也方便了库文件的维护和更新。
那动态库的代码也是需要加载进内存的,为什么会实现简洁呢?
:简洁是对于程序来说的,使用静态库时,静态库的整个代码都在可执行程序里面,使用动态库时,动态库只有所需函数或变量的地址被包含在程序里面。
动态库的代码是什么时候加载进内存的?
:当程序第一次调用动态库中的函数或访问动态库中的变量时,操作系统的动态链接器(或称为动态加载器)会被触发。动态链接器会根据程序中的指令,从磁盘上读取对应的动态库文件,并将其加载到进程的内存空间中。
动态库的代码是什么时候卸载的?
:动态库的卸载不是立即发生的,而是在满足一定条件时才会进行卸载。
当一个程序使用完动态库后,如果其他程序或进程也在使用相同的动态库,那么动态库会继续留在内存中供其他程序使用,不会被立即卸载。这是因为动态库可以被多个程序共享,避免了重复加载和占用额外内存空间。
只有在满足以下条件时,动态库才会被卸载:
- 引用计数为零:当所有使用该动态库的程序或进程都结束了对动态库的使用,并且没有其他程序或进程在使用该动态库时,动态库的引用计数会变为零。这表示没有程序再需要该动态库,此时动态库可以被卸载。
- 卸载策略:操作系统或动态链接器可能还会采用一些卸载策略来决定何时卸载动态库。这些策略可能考虑到资源利用率、性能等因素,以平衡系统的需求。
需要注意的是,动态库的卸载是由操作系统或动态链接器自动管理的,程序本身无法直接控制动态库的卸载。程序只需要在使用完动态库后正确关闭句柄或释放相关资源,让引用计数递减,卸载过程由操作系统或动态链接器负责。
动静态库的实现
我们基于下面这些代码来封装动静态库:
/add.h/
int add(int a, int b);
/add.c/
#include "add.h"
int add(int a, int b)
{
return a + b;
}
/sub.h/
int sub(int a, int b);
/sub.c/
#include "sub.h"
int sub(int a, int b)
{
return a - b;
}
静态库
先将代码编译为目标文件:
生成静态库:ar(archive)是gnu归档工具,rc表示(replace and create)
在Unix/Linux系统中,ar命令用于创建、修改和提取静态库(archive)。ar命令的常用选项包括:
r
: 将一个或多个目标文件添加到归档文件中,如果已经存在则替换。c
: 创建一个新的归档文件,如果文件已经存在,则覆盖它。s
: 在归档文件中添加一个符号表,用于加快链接器的符号解析速度。t
:显示归档文件中的成员列表。可以使用ar t libexample.a命令列出libexample.a静态库中的所有目标文件。x
:从归档文件中提取一个或多个成员。例如,ar x libexample.a file1.o将从libexample.a静态库中提取file1.o目标文件。d
:从归档文件中删除一个或多个成员。可以使用ar d libexample.a file1.o命令从libexample.a静态库中删除file1.o目标文件。p
:打印归档文件中的成员内容。可以使用ar p libexample.a file1.o命令将file1.o目标文件的内容打印到标准输出。v
: 用于在执行操作时显示详细信息。它会将操作的进度和结果打印到标准输出,以便用户可以查看每个成员的处理情况
查看静态库中的目录列表:
使用静态库:
-L
指定库路径-l
指定库名
测试目标文件生成后,静态库删掉,程序照样可以运行:
为了实现让程序自动找到静态库:
库搜索路径
- 从左到右搜索-L指定的目录。
- 由环境变量指定的目录 (LIBRARY_PATH)
- 由系统指定的目录
/usr/lib
/usr/local/lib
头文件也是很重要的,所以需要将其打包成静态库和头文件组织起来。
静态库放入lib目录下,头文件放入include下
头文件放入/usr/include
库文件放入/usr/lib
和/usr/local/lib
中的任意一个
使用时还需用-l
指明要链接的库在哪一个路径下,因为gcc在默认路径下链接的是C/C++库,只知道内置的库,不知道第三方库,所以需要显式的说明。
动态库
生成动态库
shared
: 表示生成共享库格式fPIC
:产生位置无关码(position independent code)- 库名规则:
libxxx.so
将相关文件打包为目标文件,这里和静态库略有不同:
链接目标文件,将其链接成共享库文件:
将生成的共享库文件安装到适当的位置,以供其他程序使用
如果您只想让某个程序使用该共享库,而不是所有程序,可以将共享库文件放在该程序的工作目录中,或将其路径添加到LD_LIBRARY_PATH
环境变量中。
上面的指令可能会让大家误会,实际上将共享库文件拷贝到系统共享库路径下和更改LD_LIBRARY_PATH
选择其中一种就可以了。
如果你将共享库文件放在了系统共享库路径下,那么搜索库时会去系统共享库路径下寻找,
如果更改LD_LIBRARY_PATH
,搜索库时会去LD_LIBRARY_PATH
指定的路径下去寻找,这里的是LD_LIBRARY_PATH=.
这个.
代表当前目录,然而上图中我们将共享库文件拷贝到系统共享库路径下了,这个更改LD_LIBRARY_PATH
相当于是无意义的。
库文件名称和引入库的名称
如:libc.so -> c库,去掉前缀lib,去掉后缀.so,.a
😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄