Linux下静态库与动态库
环境:Ubuntu 18.04.6
简介:
所谓库文件,其实就是经过编译的二进制源文件,可以分为静态库
和动态库
。在使用时需要搭配头文件。
在项目中使用库有两个目的:
- 使程序更加简洁,减少程序中的源文件数量。
- 避免源代码泄露。
1. 静态库
linux中静态库由ar
(gcc内自带的程序)命令生成,现在已经使用的很少,大多数情况都是使用动态库。
命名规则如下:
- Linux中,以
lib
为前缀,.a
为后缀,中间随意,也就是libxxx.a
的命名格式。 - Windows中,以
lib
为前缀,以.lib
为后缀,中间随意,也就是libxxx.lib
的命名格式。
1.1 生成静态链接库
将源文件经过预编译、编译、汇编得到的二进制文件,通过ar
工具打包即可得到静态库文件。
ar
工具参数如下:
-c
:创建一个库,不论库是否存在都将进行创建。-s
:创建目标文件索引,这样如果库较大时,能加快搜索速度。-r
:在库中插入模块。默认新成员是添加在库文件的结尾,但如果该模块名已经存在,那么就进行替换。
最后发布需要两个文件:
- 制作的
libxxx.a
库文件,里面包含了具体实现的源代码。 - 相应的头文件,相当于提供了源代码的接口。
1.2 实例
测试程序:
这里依旧使用一-->3-->3.1
中的简单的计算机程序,结构如下:
.
├── add.c
├── include
│ └── head.h
├── main.c
├── sub.c
其中:
- add.c和sub.c分别为加法和减法程序
- include/head.h为头文件
- main.c为测试文件
生成静态库:
-
将源文件进行汇编操作(前三步),得到二进制文件(注意指定头文件):
gcc add.c sub.c -c -I include/
得到二进制文件:
add.o sub.o
-
将生成的目标文件通过ar工具打包为静态库(注意命名):
ar -csr libcal.a add.o sub.o
得到静态库文件:
libcal.a
-
将头文件和静态库文件一起发给用户即可使用:
include/head.h libcal.a
1.3 静态库的使用
首先要得到静态库和头文件,随后开始使用,当前文件结构如下:
head.h
libcal.h
main.c
错误示范:
gcc main.c -o cal
/tmp/ccT4oiqj.o:在函数‘main’中:
main.c:(.text+0x21):对‘add’未定义的引用
main.c:(.text+0x43):对‘sub’未定义的引用
collect2: error: ld returned 1 exit status
发现编译报错了,这是因为main.c中引入了头文件head.h,但编译器未能找到head.h中函数的具体实现,也就是找不到库文件,这与库文件的检索有关(后面会讲),简而言之就是找不到库文件,因此我们只需要在编译时指定库文件的路径和名字即可:
-L
:指定库文件所在的目录,相对或绝对都可以。-l
(小写的l):指定库文件的名字(去掉前缀和后缀)。
正确示范:
gcc main.c -o test -L ./ -l cal
生成成功。得到可执行程序test。
该执行程序不依赖库文件和头文件即可运行,因为编译过程实际上是将库中的代码复制到了可执行程序中。
2. 动态库
简介:
与静态库不同,动态库是程序运行时才会加载的库,当动态链接成功部署后,多个程序可以使用同一个加载到内存中的动态库,因此在Linux中动态库也可以被称之为共享库。
动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊形式形成的。库中函数和变量使用的地址是相对地址(静态库中使用的是绝对地址),其真实地址是在应用程序加载动态库时形成的。
命名规则如下:
- Linux中,以
lib
为前缀,以.so
为后缀,中间是库的名字。也就是libxxx.so
- Windows中,以
lib
为前缀,以.dll
为后缀,中间是库的名字。也就是libxxx.dll
。
2.1 生成动态链接库
具体步骤如下:
- 通过
-fpic
参数在汇编时生成与位置无关的代码。 - 通过
-shared
参数告知编译器生成一个动态链接库。 - 发布头文件和动态链接库。
2.2 实例
实例代码:
依旧以一-->3-->3.1
中的代码为例,其结构如下:
beasts777@ubuntu:~/coding/c++/cal$ tree
.
├── add.c
├── include
│ └── head.h
├── main.c
├── sub.c
生成动态链接库
-
使用
gcc
对源文件进行汇编(参数-c
)生成与位置无关的目标文件,需要指定参数-fpic
(注意指定头文件所在目录)gcc add.c sub.c -c -fpic -I include
得到目标文件:
add.o sub.o
-
使用
gcc
将二进制源文件打包成动态库,需要使用参数-shared
gcc add.o sub.o -shared -o libcalc.so
生成动态库文件:
libcalc.so
-
发布动态库文件和头文件
libcalc.so head.h
2.3 使用动态链接库
-
首先获取动态链接库和头文件:
. ├── head.h ├── libcalc.so └── main.c # 这是测试文件
-
编译测试文件(注意指定库文件的地址和名字):
gcc main.c -L ./ -l calc -o app
得到可执行文件
app
-
执行文件:
./app ./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory
发现文件报错:找不到共享库
libcal.so
。这是为什么?明明在编译测试文件时制定了库文件的路径和名字,但实际运行时却记得名字却找不到目录。还有为什么静态库不会出现这一问题?答案见下一节?
2.4 解决动态库无法加载的问题
2.4.1 库的工作原理
静态库:
在程序编译的最后一个阶段,也就是链接阶段,提供的静态库会被打包进可执行程序中。也就是说:此时可执行程序内已经包含了静态库中的代码,当可执行程序执行时,其拷贝的静态库的代码也会加载到进程的代码区,因此也就不需要再去寻找静态库了。
动态库:
- 在链接阶段,虽然使用
gcc
命令的-L
和-l
指定了动态库的目录和名字,但此时:- 这一步只检查了动态库是否存在,并未将动态库中的代码拷贝到可执行程序中,因此运行时仍需要依赖动态库。
- 虽然链接时指定了动态库的目录和名字,但可执行程序中只保留了库名,而未保留库的路径,它寻找库实际上是通过程序链接器按照指定顺序在固定目录寻找的。
- 在可执行程序执行阶段:
- 程序执行时会先检测需要的动态库是否存在,加载不到就会报错,显示无法加载到动态库。
- 当动态库中的函数在程序中被调用了,这时动态库才会加载到内存中,不调用就不加载。
- 动态库的检测和内存加载操作都是通过动态链接器完成的。
2.4.2 动态链接器
简介:动态链接器是一个独立于应用程序的进程,其本身属于操作系统,搜索动态库的依照一定策略,优先级从高到低依次是:
- 可执行文件内部的
DT_RPATH
- 系统的环境变量:
LD_LIBRARY_PATH
- 系统动态库的缓存文件:
/etc/ld.so.cache
- 存储动态库、静态库的系统目录:
/lib/
,/usr/lib
按照以上顺序,依次搜索动态库是否存在,如果都搜索不到,那么动态链接器就会报错,提示无法找到动态库。
由此,便可以得到找不到动态库的解决方案。
2.4.3 解决方案
一共有三种方法:
-
方案一:将库的路径添加到环境变量
LD_LIBRARY_PATH
中。具体步骤如下:-
找到配置文件:
- 用户级别:
~/.bashrc
。该设置仅对当前用户有效。 - 全局级别:
/etc/profile
。该设置对所有用户都有效。
- 用户级别:
-
打开配置文件,添加一句话:
export LD_LIBRARY_PATH =$LD_LIBRARY_PATH :动态库的绝对路径 # eg: export LD_LIBRARY_PATH =$LD_LIBRARY_PATH :/home/beasts777/coding/c++/activeLib/libcalc.so
-
令配置的文件生效:
-
用户级别的修改:重启终端即可。(因为用户配置文件是在打开终端时加载的)
-
系统级别的修改:重启系统即可。(全局配置文件在开机时加载)
-
也可以用命令让操作系统重新加载配置文件,无需重启终端或系统:
# 用户级 source ~/.bashrc # 系统级 source /etc/profile
-
-
-
方案二:更新系统动态库的缓存文件:
/etc/ld.so.cache
(需要注意的是,我们无法直接更改缓存文件,应当更改:/etc/ld.so.conf
配置文件,随后再同步到缓存文件):-
打开
/etc/ld.so.conf
,将动态库的目录(注意这里时目录,不要添加库的名字)添加到最后一行,保存退出/home/beasts777/coding/c++/activeLib/
-
将
ld.so.conf
同步到ld.so.cache
中:sudo ldconfig
不需要进行其它操作即可生效。
-
-
方案三:将动态库文件拷贝到系统库目录
/lib/
或/usr/lib/
,或者在里面创建库的软连接(更推荐,因为这样如果后续库被修改了,就不用再拷贝一次了)# 拷贝库 sudo cp /home/beasts777/coding/c++/activeLib/libcalc.so /usr/lib/libcal.so # 创建软链接(推荐) sudo ln -s /home/beasts777/coding/c++/activeLib/libcalc.so /usr/lib/libcal.so
2.4.4 验证是否能够链接到动态库文件
语法:ldd
可执行程序名
EG:
ldd app
linux-vdso.so.1 (0x00007ffe5ffbb000)
libcalc.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3a8b488000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3a8ba7b000)
如果可以链接,那么会显示地址,否则会显示not found。
2.4.5 实操
通过在系统动态库内添加软连接实现。
当前文件结构如下:
.
├── app # 可执行文件
├── head.h # 头文件
├── libcalc.so # 动态库文件
-
在
/usr/lib
下创建动态库文件的软链接:sudo ln -s ~/coding/c++/cal/activeLib/libcalc.so /usr/lib/libcalc.so
-
查看可执行程序是否可以读取到动态库文件:
ldd app linux-vdso.so.1 (0x00007ffe3e3ee000) libcalc.so => /usr/lib/libcalc.so (0x00007f29c7c68000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f29c7877000) /lib64/ld-linux-x86-64.so.2 (0x00007f29c806c000)
读取成功。
-
直接运行程序即可。
3. 优缺点
3.1 静态库
优点:
- 静态库被直接打包到应用程序中,因此加载速度更快。
- 发布程序时无需发布静态库。
缺点:
- 相同的库文件可能在内存中被加载多份,浪费内存。
- 如果库文件更新,就需要对项目进行重新编译,将新的库文件代码打包到可执行程序中。
3.2 动态库
优点:
- 不同进程可使用同一动态库,实现不同进程间的资源共享,无需多次复制。
- 修改动态库时,只需替换库文件,无需重新编译应用程序。
- 因为动态库只有在使用库函数时才会被调用,因此程序员可以控制何时加载动态库。
缺点:
- 加载速度比静态库慢,但当今计算机基本可以忽略。
- 发布应用程序时需要发布依赖的动态库。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理