文章目录
一、静态库
Linux中静态库是由 ar 生成的
现在的静态库已经不再普遍了,大部分程序都在使用动态库
- Linux中静态库以
lib
作为前缀,.a
作为后缀,即libxxx.a
- Windows中静态库一般以
lib
作为前缀, 以lib
作为后缀, 即libxxx.lib
1.生成静态链接库
静态库的生成需要先对源文件进行汇编操作(gcc -c)
以得到二进制文件格式(.o)
然后通过ar工具将目标文件打包以得到静态库文件(libxxx.a)
ar工具创建静态库有三个参数
- 参数c:创建一个库,不管库是否存在,都将创建。
- 参数s:创建目标文件索引,这在创建较大的库时能加快时间。
- 参数r:在库中插入模块(替换)。默认新的成员添加在库的结尾处,如果模块名已经在库中存在,则替换同名的模块。
具体步骤如下
#1.对源文件进行汇编,得到二进制(.o)文件
gcc 源文件(*.c) -c #-c位置没有要求
#2.将.o文件进行打包,得到静态库
ar csr 静态库的名字(libxxx.a) 原材料(*.o)
#3.发布静态库
提供头文件 **.h文件
提供静态库 libxxx.a
eg:
#生成.o
gcc add.c div.c mult.c sub.c -c
#可能会提示头文件找不到,添加参数 -I 重写头文件路径
gcc add.c div.c mult.c sub.c -c -I ./include/
#将.o打包
ar rcs libcalc.a *.o
#发布 /include 中的头文件和打包出来的 libcalc.a
2.静态库的使用
#假设我们已经将测试程序,头文件,静态库都放在了当前同一个目录中
#编译测试程序,生成可执行文件
gcc main.c -o app #此时会报错 undefined reference
报错原因:
编译的源文件中包含了头文件 head.h, 这个头文件中声明的函数对应的定义(也就是函数体实现)在静态库中,程序在编译的时候没有找到函数实现,因此提示 undefined reference to xxxx。
解决方案:
- -L: 指定库所在的目录(相对或者绝对路径)
- -l: 指定库的名字, 需要掐头(lib)去尾(.a) 剩下的才是需要的静态库的名字
gcc main.c -o app -L ./ -l calc
二、动态库
动态链接库是程序运行时加载的库,当动态链接库正确部署之后,运行的多个程序可以使用同一个加载到内存中的动态库,因此在Linux中动态链接库也可称之为共享库。
库中函数和变量的地址使用的是相对地址(静态库中使用的是绝对地址),其真实地址是在应用程序加载动态库时形成的。
- 在Linux中动态库以
lib
作为前缀, 以.so
作为后缀, 即:libxxx.so
- 在Windows中动态库一般以
lib
作为前缀, 以dll
作为后缀, 即:libxxx.dll
1.生成动态链接库
生成动态链接库是直接使用
gcc
命令并且需要添加-fPIC(-fpic)
以及-shared
参数。
- -fPIC 或 -fpic 参数的作用是使得 gcc 生成的代码是与位置无关的,也就是使用相对位置。
- -shared参数的作用是告诉编译器生成一个动态链接库。
具体步骤如下:
#将源文件进行汇编,并添加 -fpic参数
gcc 源文件(*.c) -c -fpic
#打包.o文件成动态库,使用gcc的 -shared参数
gcc -shared 与位置无关的目标文件(*.o) -o 动态库(libxxx.so)
#发布动态库和头文件
eg:
#汇编
gcc add.c div.c mult.c sub.c -c -fpic -I ./include/
#打包
gcc -shared add.o div.o mult.o sub.o -o libcalc.so
#发布
2.动态库的使用
当我们同静态库一样编译测试程序为可执行程序后
gcc main.c -o app -L ./ -l calc
执行
./app
#./app: error while loading shared libraries: libcalc.so:
#cannot open shared object file: No such file or directory
会有以上错误:无法加载到动态库
3.解决动态库无法加载的问题
①库的工作原理
-
静态库如何被加载?
在程序编译的最后一个阶段也就是链接阶段,提供的静态库会被打包到可执行程序中。当可执行程序被执行,静态库中的代码也会一并被加载到内存中,因此不会出现静态库找不到无法被加载的问题。 -
动态库如何被加载?
-
在程序编译的最后一个阶段也就是链接阶段:
在gcc命令中虽然指定了库路径(使用参数 -L ), 但是这个路径并没有记录到可执行程序中,只是检查了这个路径下的库文件是否存在。
同样对应的动态库文件也没有被打包到可执行程序中,只是在可执行程序中记录了库的名字。 -
可执行程序被执行起来之后:
程序执行的时候会先检测需要的动态库是否可以被加载,加载不到就会提示上边的错误信息
当动态库中的函数在程序中被调用了, 这个时候动态库才加载到内存,如果不被调用就不加载
动态库的检测和内存加载操作都是由动态连接器来完成的
②动态链接器
动态链接器是一个独立于应用程序的进程, 属于操作系统, 当用户的程序需要加载动态库的时候动态连接器就开始工作了,显然动态连接器不知道用户通过 gcc 编译程序的时候通过参数 -L指定的路径。
那么动态链接器是如何搜索某一个动态库的呢,在它内部有一个默认的搜索顺序,按照优先级从高到低的顺序分别是:
- 可执行文件内部的 DT_RPATH 段
- 系统的环境变量
LD_LIBRARY_PATH
- 系统动态库的缓存文件
/etc/ld.so.cache
- 存储动态库/静态库的系统目录
/lib/
,/usr/lib
等
找到后结束遍历,找不到就报错
③解决方法
只需要将动态库的路径放到对应的环境变量或者系统配置文件中,同样也可以将动态库拷贝到系统库目录(或者是将动态库的软链接文件放到这些系统库目录中)。
方法1:将库路径添加到环境变量 LD_LIBRARY_PATH 中
- 找到相关的配置文件
用户级别: ~/.bashrc
系统级别: /etc/profile
- 使用vim打开配置文件,文件最后添加
export LIBRARY_PATH=$LIBRARY_PATH:动态库的绝对路径
- 让修改的配置文件生效
1.修改了用户级别的配置文件,关闭当前终端,打开一个新的就生效
2.修改了系统级别的配置文件,注销关闭系统,再开机,配置就生效了
3.或者执行命令
# 修改的是哪一个就执行对应的那个命令
# source 可以简写为一个 . , 作用是让文件内容被重新加载
source ~/.bashrc (. ~/.bashrc)
source /etc/profile (. /etc/profile)
方法2:更新 /etc/ld.so.cache 文件
- 找到动态库所在的绝对路径(不包括库的名字)比如:/home/robin/Library/
- 使用vim 修改
/etc/ld.so.conf
文件, 将上边的路径添加到文件中(独自占一行)
#1.打开文件
sudo vim /etc/ld.so.conf
#2.添加动态库路径,并且保存退出
- 更新
/etc/ld.so.conf
中的数据到/etc/ld.so.cache
中
sudo ldconfig
方法3:拷贝动态库文件到系统库目录 /lib/ 或者 /usr/lib 中 (库的软链接文件放进去)
# 库拷贝
sudo cp /xxx/xxx/libxxx.so /usr/lib
# 创建软连接
sudo ln -s /xxx/xxx/libxxx.so /usr/lib/libxxx.so
但并不建议直接拷贝,否则再更新时会很麻烦,还要重新拷贝
二者中优解是软链接
④验证
#语法格式
ldd 可执行程序名
通过这种方法可以知道程序是否可以通过动态链接器加载到对应的动态库
如果其后都有地址就说明可以
三、优缺点
1.静态库
-
优点
-
静态库被打包到应用程序中加载速度快
-
发布程序无需提供静态库,移植方便
-
缺点
-
相同的库文件数据可能在内存中被加载多份, 消耗系统资源,浪费内存
-
库文件更新需要重新编译项目文件, 生成新的可执行程序, 浪费时间。
2.动态库
-
优点
-
可实现不同进程间的资源共享
-
动态库升级简单, 只需要替换库文件, 无需重新编译应用程序
-
程序⚪可以控制何时加载动态库, 不调用库函数动态库不会被加载
-
缺点
-
载速度比静态库慢, 但以现在计算机的性能可以忽略
-
发布程序需要提供依赖的动态库