静态库和共享库
1. 区别
静态库
静态库在文件中静态展开,所以有多少文件就展开多少次,非常吃内存,100M
展开 100
次,就是 1G
,但是这样的好处就是静态加载的速度快。
动态库
使用动态库会将动态库加载到内存,10
个文件也只需要加载一次,然后这些文件用到库的时候临时去加载,速度慢一些,但是很省内存。
优缺点
动态库和静态库各有优劣,根据实际情况合理选用即可。
- 静态库:对空间要求较低,而时间要求较高的核心程序中。
- 动态库:对时间要求较低,对空间要求较高。
2. 静态库制作
Linux
:libxxx.a
lib
:前缀(固定)xxx
:库的名字,自己起.a
:后缀(固定)
Windows
:libxxx.lib
静态库生成指令
ar rcs libmylib.a file1.o
- r - 将文件插入备存文件中
- c - 建立备存文件
- s - 索引
生成步骤
第一步: 写好源代码。
第二步: 编译源代码生成 .o
文件。
第三步: 制作静态库。
第四步: 编译静态库到可执行文件中。
gcc test.c lib库名.a -o a.out
编译时出现了函数未定义的警告,可以忽略,让系统生成默认的定义。
下图可以发现 test.c 只占用了 209 大小,而 test 却占用了 16752 ,说明静态库是直接编译到文件中。
上面出现的警告,可以用编译器隐式声明来解决。编译器只能隐式声明返回值为 int 的函数形式:
int add(int, int);
如果函数不是返回的 int
,则隐式声明失效,所以会警告。
那我们只用在 test.c
中加入函数声明即可:
这时候再编译就不会警告了:
但是这样子做需要库的使用者知道库里的函数,然后再一个一个加到代码里,不是很科学,我们可以用下面这种方法来加载静态库。
右边的 define 为头文件守卫,防止在代码中多次 include 同一个头文件,多次展开静态库,带来的额外开销。
然后我们同样进行编译,发现也不会报错,但是这样子写更好一些。
当然,我们可以将静态库和头文件分别放至其他目录下,然后再模拟一遍。
然后运行结果。
3. 动态库制作
Linux
:libxxx.so
lib
:前缀(固定)xxx
:库的名字,自己起.so
:后缀(固定)- 在
Linux
下是一个可执行文件
Windows
:libxxx.dll
制作步骤
- 1.将
.c
生成.o
文件(生成与位置无关的代码-fPIC
)
gcc -c add.c -o add.o -fPIC
使用这个参数过后,生成的函数就和位置无关,挂上 @plt
标识,等待动态绑定。
- 2.使用
gcc -shared
制作动态库
gcc -shared -o lib库名.so add.o sub.o div.o
- 3.编译可执行程序时指定所使用的动态库
-l
:指定库名;-L
:指定库路径
gcc test.c -o a.out -l mymath -L ./lib
- 4.运行可执行程序
./a.out
过程演示
步骤一:生成位置无关的 .o
文件
步骤二:制作动态库 gcc -shared -o lib
库名 .so add.o
步骤三:编译程序
文件分布如下:动态库在 lib
目录下,头文件在 inc
目录下。
下面编译文件。
步骤四:执行文件,出错
出错原因分析:
- 连接器:工作于链接阶段,工作时需要
-l
和-L
。 - 动态链接器:工作于程序运行阶段,工作时需要提供动态库所在目录位置。
解决方法
(1)通过环境变量,但是临时生效
指定动态库路径并使其生效,然后再执行文件。
通过环境变量指定动态库所在位置:
export LD_LIBRARY_PATH=动态库路径
当关闭终端,再次执行 a.out
时,又报错。
这是因为,环境变量是进程的概念,关闭终端之后再打开,是两个进程,环境变量发生了变化。
(2)修改配置文件,永久生效
要想永久生效(建议写入绝对路径):
- 1.需要修改 bash 的配置文件:vi ~./bashrc ,写入 export LD_LIBRARY_PATH=动态库路径 并保存。
- 2.修改后要使配置文件立即生效:
. .bashrc
或者source .bashrc
或者重开终端让其自己加载。 - 3.这下再执行
a.out
就不会报错了。
(3)移动动态库(不推荐)
拷贝自定义动态库 到 /lib
(标准 C
库所在目录位置)
(4)配置文件法(最难)
① sudo vi /etc/ld.so.conf
② 写入动态库绝对路径保存
③ sudo ldconfig -v
使配置文件生效
④ ./a.out
成功 — 使用 ldd a.out
查看
4. 工作原理
静态库: GCC 进行链接时,会把静态库中代码打包到可执行程序中。
动态库: GCC 进行链接时,动态库的代码不会打包到可执行程序中。
程序启动之后,动态库会被动态加载到内存中,通过 ldd 命令检查动态库依赖关系。
如何定位共享文件呢?
当系统加载可执行代码的时候,能够知道所依赖库的名字,但是还需要知道绝对路径。对于 elf 格式的可执行程序,是由 ld-linux.so 来完成的,它先后搜索 elf 文件的 DT_RPATH 段 --> 环境变量 LD_LIBRARY_PATH --> /etc/ld.so.cache 文件列表 --> /lib/,/usr/lib 目录找到库文件后将其载入内存。
5. 区别
静态库
优点:
- 静态库被打包到应用程序中,加载速度快
- 发布程序无需提供静态库,移植方便
缺点:
- 消耗系统资源,浪费内存
- 更新、部署、发布麻烦
动态库
优点:
- 可以实现进程间资源共享(共享库)
- 更新、部署、发布简单
- 可以控制何时加载动态库
缺点:
- 加载速度比静态库慢
- 发布程序时需要提供依赖的动态库