在最初学习GCC的使用的时候,提到了动态、静态库的创建办法。今天就让我们来详细了解一番,它们之间究竟有何不同吧!
演示所用系统:centos7.6
[TOC]
1.动态库和静态库
先来了解一下动态库和静态库的基本概念吧!
- 静态库
.a
程序编译链接的时候,把静态库的代码连接到自己的可执行程序中,程序运行的时候将不再需要静态库 - 动态库
.so
程序在运行的时候才去链接动态库的代码,多个程序共享库的代码
2.生成
测试所用代码 👉 点我
我写好了两个头文件和两个源文件,为了减少博客篇幅,此处只贴出.c
的函数实现
1 | //myMath.c |
2.1 静态库
生成静态库所用命令为ar -rc
,对应的完整make操作如下
1 | libMytest.a:myMath.o myPrint.o |
生成好静态库后,我们可以用ar -tv
命令来查看该库的目录列表
1 | [muxue@bt-7274:~/git/linux/code/22-11-02_动静态库]$ ar -tv libmytest.a |
2.2 动态库
动态库的生成无需额外的命令,只需要在gcc编译的时候,指定-shared
即可
同时,依赖的.o
文件也需要用-fPIC
来编译
1 | -fPIC 与位置无关码,和动态库的特性有关 |
其make操作如下
1 | libmytest.so:myMath.o myPrint.o |
2.3 一并发布
这里我写了一个更加完整的makefile,可以同时编译生成动静态库,并将其打包到一个指定的文件夹内
1 |
|
3.使用
1 |
|
当我们使用了动静态库后,就没有办法直接编译这个可执行程序了
1 | muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ gcc test.c |
这是因为,gcc没办法找到我们对应的头文件
""
是在当前路径下找<>
是在库目录下面找
因为我们的头文件既不在当前路径,也不在系统的库中,所以gcc就没有办法找到头文件和函数声明
3.1 静态库
链接静态库的方法如下
1 | gcc test.c -L../lib-static/lib/ -I../lib-static/include/ -lmytest -o test |
-L
选项后带的是库的路径-I
选择后带的是头文件的搜索路径-l(小写的L)
选项带的是库的名字,需要去掉库文件名前面的lib
和后缀.a
-o test
代表生成可执行文件名为test
1 | [muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ gcc test.c -L../lib-static/lib/ -I ../lib-static/include/ -lmytest -o test |
特点
静态库的特点便是,其库的实现已经被编译链接进入了可执行程序,即便我们将库给删除,也不影响可执行程序的运行
1 | [muxue@bt-7274:~/git/linux/code/22-11-02_动静态库]$ make clean |
如果我们把自己的库的实现丢入了系统的库目录下(一般是/lib64/
)编译的时候就不需要带-L
选项了,只需要用-l
指定库名即可
1 | gcc test.c -lmytest |
但是将自己的库丢入系统库路径下的操作并不推荐,就和你将自己的可执行程序丢入/usr/bin
路径里面一样,会污染系统的环境
3.2 动态库
动态库和静态库链接的基本方式是一样的
1 | gcc test.c -L../lib-dynamic/lib/ -I ../lib-dynamic/include/ -lmytest -o testd |
这里选项的含义和上面完全一致,不同的是运行的时候
1 | [muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ./testd |
直接运行,你会发现报错了!这个报错的大概意思就是找不到动态库文件
ldd命令
使用ldd
命令查看testd
可执行文件的动态库结构,会发现我们自己的库是没有找到的
1 | [muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ldd testd |
这是因为,动态库的特点便是运行的时候也需要指定!这是一个动态链接的过程!
动态链接
动态库需要执行动态链接
:在可执行程序开始运行之前,外部函数的机器码由操作系统从磁盘上的该动态库复制到内存中
刚刚我们的指定只是告诉了gcc
编译器库路径在哪儿,但是可执行程序运行的时候并不知道!
那么如何让可执行程序找到我们的动态库呢?
- 将动态库拷贝到系统的
/lib64
文件夹中 - 通过修改环境变量的方式,类似于
PATH
,可执行程序运行的时候,会自动到LD_LIBRARY_PATH
里面找动态库 - 修改系统配置文件
3.3 找到动态库
3.3.1 环境变量LD_LIBRARY_PATH
和修改PATH
的环境变量一样,我们可以通过修改环境变量的方式增加动态库的查找路径
1 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/muxue/git/linux/code/22-11-02_动静态库/lib-dynamic/lib/ |
修改了之后的环境变量如下
1 | LD_LIBRARY_PATH=:/home/muxue/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/muxue/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/muxue/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/muxue/git/linux/code/22-11-02_动静态库/lib-dynamic/lib/ |
再次运行./testd
成功执行!
1 | [muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/muxue/git/linux/code/22-11-02_动静态库/lib-dynamic/lib/ |
修改配置文件的办法,便是将该路径永久写入环境变量(修改环境变量的操作只对当前bash有效)这里就不演示辣!
1 | [muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ldd testd |
ldd命令的结果也显示出了我们自己写的动态库的路径
3.3.2 /etc/ld.so.conf.d
除了修改环境变量,我们还可以修改/etc/ld.so.conf.d
下的文件
1 | [muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ls /etc/ld.so.conf.d |
这里的操作非常简单,我们只需要在该目录下新增一个.conf
文件,并在里面写入动态库的绝对路径即可!
1 | [muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ls /etc/ld.so.conf.d |
设置了之后,第一次运行,还是显示找不到动态库
1 | [muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ./testd |
我们只需要执行下面的命令让配文件生效,就OK了!
1 | sudo ldconfig #子用户权限不够,需要加sudo |
执行完该命令后,可执行程序也能成功运行了1
1 | [muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ sudo ldconfig |
测试完毕之后,建议将配置文件删除,并重新加载动态库配置文件
1 | sudo rm /etc/ld.so.conf.d/mytest.conf |
这样做是避免污染
3.3.3 在lib64下创建一个软连接
1 | ln -s /home/muxue/git/linux/code/22-11-02_动静态库/lib-dynamic/lib/libmytest.so /lib64/libmytest.so |
创建软连接的方式和将我们的文件复制进去本质是一样的,只不过软连接只是一个快捷方式,如果我们把源给删了,软连接也会失效
这部分就不做演示了
4.优劣
4.1 静态库
静态库编译之后的可执行程序可以脱离静态库运行,也不需要知道库的路径。
即便这个库被删除,也丝毫不影响我们的可执行程序
4.2 动态库
动态库的代码只需要一份,所有的可执行程序便都可以使用
在运行期间,动态库可以被多个进程所共享。但前提是,可执行程序需要知道该动态库的路径,以便将其加载到内存中(或者找到它在内存中的位置)
这样就保证了多个进程同时使用同一个库,节省了内存的消耗,也节省了磁盘空间
这里动态库的可执行文件大小,小于静态库的可执行文件
因为测试的代码不多,所以差距尚不明显
5.动态库-fPIC的作用
参考https://blog.csdn.net/itworld123/article/details/117587091
1 | gcc -fPIC -c myMath.c -o myMath.o |
fPIC 的全称是 Position Independent Code
, 用于生成位置无关代码
什么是位置无关代码?
个人理解是代码无绝对跳转,跳转都为相对跳转
如果我们的静态库中,不使用其他库的代码(比如stdio.h
)
1 | int fuc(int a) |
这时候,就可以再编译的时候不带-fPIC
否则会报错
1 | /usr/bin/ld: /tmp/ccCViivC.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC |
但显然,这种情况是非常少见的,所以我们一般编译动态库的时候,都需要带上这个参数,来实现真正意义上的动态库编译
- 加 fPIC 选项生成的动态库,显然是位置无关的,这样的代码本身就能被放到线性地址空间的任意位置,无需修改就能正确执行。通常的方法是获取指令指针的值,加上一个偏移得到全局变量 / 函数的地址。
- 加 fPIC 选项的源文件对于它引用的函数头文件编写有很宽松的尺度。比如只需要包含个声明的函数的头文件,即使没有相应的 C 文件来实现,编译成 so 库照样可以通过。
- 对于不加 fPIC,则加载 so 文件时,需要对代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个 .so 文件代码段的进程在内核里都会生成这个 .so 文件代码段的 copy。每个 copy 都不一样,取决于这个 .so 文件代码段和数据段内存映射的位置。这种方式更消耗内存,优点是加载速度可能会快一丢丢,
弊大于利
结语
动静态库的基本认识到这里就OVER辣,大家也可以去尝试下载一些第三方的库来使用,比如在树莓派上最常用的wiringPi
库,还有C++的boost
库等等
1 | sudo yum install -y boost-devel |
有什么问题,可以在评论区提出哦!
- 本文标题:【Linux】动静态库
- 创建时间:2022-11-03 10:40:16
- 本文链接:posts/2737580475/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!