> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。
> 目标:理解静动态库,自己能模拟实现动静态库。
> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!
> 专栏选自:Linux初阶
> 望小伙伴们点赞👍收藏✨加关注哟💕💕
🌟前言
库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:
- 静态库(.a、.lib)
- 动态库(.so、.dll)
今天我们来谈谈 Linux 下静态库和动态库。
⭐主体
学习【Linux】静态库和动态库咱们按照下面的图解:
🌙 认识动静态库
Linux的库一般分为动态库和静态库:
- 静态库(.a):库文件以.a为后缀,程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
- 动态库(.so):库文件以.so为后缀,程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
在Xshell下安装静态库:
cpp
yum install -y glibc-static
yum install -y libstdc++-static
我们可以通过 [ ldd 可执行程序文件名 ]来查看可执行程序所依赖的库:
其中 /lib64/libc.so.6 就是可执行程序的库文件,它其实是一个软链接。我们可以通过以下命令来查看
cpp
ls /lib64/libc.so.6 -l
总结:
如何辨别它采用的是哪一种库呢?
1.我们可以通过后缀来区分(在Linux中)
- 在Linux中,以 .so 结尾的后缀,是动态库;以 .a 结尾的是静态库
- 在Windows中,以 .dll 结尾的后缀,是动态库;以 .lib 结尾的是静态库
注:
- 库文件的名字:libxxx.so 和 libxxx.a
- 库的真实名字:去掉lib前缀,去掉 .a 、.so后缀,剩下的就是库的名称。
2.我们还可以通过file命令查看
上图中分别使用了动静态库对同一个文件进行编译的,通过file命名可以查看到所对应的链接信息
🌙 理解什么是库
概念:
如果不想给对方我们的源代码,我们可以选择给用户提供我们的.o可重定位目标二进制文件(gcc -c 文件)与.h头文件。让用户用我们提供的.o文件进行链接即可。在编译时,只要把源文件编译成.o文件在将其链接便可形成一个可执行的程序。
图解:
举个栗子:
案例:
说明:
难道就这么简单吗?我们可以给对方提供.o(方法的实现),同时还有提供.h(里面包含着都有哪些方法),此时对方是能用的。但是如果存在很多.c文件呢?难道我们要把几千个.c文件全部编译成.o在加上头文件全部一个一个提供吗?那样太过于麻烦,为了让用户更好的使用库,我们就有把所有的.o文件打成一个包,给对方提供一个库文件即可!把多个.o合并成一个文件,这个文件就是库,把包方式的不同就分为了动态库和静态库*。
🌙 库的制作和使用
概念:
库是一个二进制文件,想要使用库(给别人使用自己的制作的库或者使用别人的库)一定是由三个部分组成:库文件、头文件、文档说明;一般这个库文件就是函数的定义,头文件就是函数声明,我们只需要将这些打包好,别人使用我们头文件所给的接口就行。
**编写如下四个文件:**用来制作静态库并打包
- 源文件包含 add.c 和 sub.c
- 头文件包含 add.h 和sub.h
💫 静态库的制作
1.生成二进制(.o)文件
2.打包
我们将生成好的.o文件进行打包:
cppar -rc libmymath.a add.o sub.o
ar命令是gnu的归档工具,常用于将目标文件打包为静态库,下面我们使用ar命令的-r 选项和-c选项进行打包。
- -r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。
- -r(rreate):建立静态库文件。
我们可以用 ar 命令的 -t 选项和 -v 选项查看静态库当中的文件。
- -t :列出静态库中的文件。
- -v :显示详细的信息
3.发布静态库
静态库要发布出去供别人使用,只要库文件(所有的.o文件)是不够的,我们需要将其和头文件一起发布出去,别人只要看到头文件,就大致了解如何使用了
💫 静态库的使用
问题抛出?
上文中,我们已经有了静态库output了,别人该如何使用呢? 例如:想要在friend文件下使用这个库:
方法一 :
现在在friend目录下有一个mytest.c文件和一个静态库文件lib,mytest.c想要使用lib,我们先编写一下mytest.c代码:
cpp#include "add.h" #include "sub.h" int main() { int x = 30; int y = 20; int ret1 = my_add(x, y); int ret2 = my_sub(x, y); printf("ret1 = %d\n",ret1); printf("ret2 = %d\n",ret2); return 0; }
编译:
说明:
编译后,报警告:没有头文件,可是明明在lib文件下有我们要的有文件以及库文件,这是为什么呢?
- 其实,编译器在编译的时候,会在当前的目录的文件中去找,不会去当前目录的文件夹中去找,lib目录下的头文件以及库文件,和mytest.c不是同级目录,所以编译会出错;
- 我们在编译的时候就需要告诉编译器,需要的头文件在哪个目录下。
cppgcc mytest.c -I ./lib
此时,又有警告了:链接错误,未定义的两个函数?在lib目录下已经定义了两个函数,并且打包好了?为什么还是报错呢?
其原因和上面一样; 所以我们还需要告诉编译器库文件在lib目录下:
cppgcc mytest.c -I ./lib -L ./lib
头文件和库文件所在位置都告诉编译器了,怎么还是报错呢?
其实,头文件和库文件都在lib目录下,在mytest.c文件中,是明确的包含了,add.h和sub.h的,gcc在编译的时候能够认识,但不认识库文件,如果在lib目录下有多个库文件,gcc是不知道你想要使用哪个库的。所以我们还需要指明库的名字。
cppgcc mytest.c -I ./lib -L ./lib -l mymath
方法二:
对比我们之前在编译某个.c文件时,为什么有加上这些选项呢?。这是因为之前的库都是在系统的默认路径下,所以我们可以将我们做好的静态库拷贝到系统的默认路径下,也是可以达到不需要加这些选项的效果;但是严重不推荐。
总结:
我们在使用静态库进行编译链接时,需要指定头文件的所在路径,库文件的所在路径以及所要掉用的库名称
-I
:指定头文件所在路径。-L
:指定库文件所在路径。-l
:指明需要链接库文件路径下的哪一个库
💫 动态库的制作
编写如下四个文件:其中源文件包含 add.c 和 sub.c ,头文件包含 add.h 和 sub.h ;用来制作动态库并打包
1.生成二进制(.o)文件
首先将上面的.c文件生成.o文件
cppgcc -fPIC -c add.c -o add.o gcc -fPIC -c sub.c -o sub.o
-fPIC:作用是告知编译器 生成位置无关代码(编译产生的代码没有绝对位置,只有相对位置);从而可以在任意地方调用生成的动态库。
2.打包
我们将生成好的.o文件进行打包:
cppgcc -shared -o libmymath.so add.o sub.o
-shared:linux在gcc编译时加上 -shared 参数时,目的是使源码编译成动态库 .so 文件;
3.发布动态库
将库文件和所有的头文件组织起来,放到lib目录下,这样就可以发布动态库了
💫 动态库的使用
动态库的使用大致和静态库类似,但略有区别。我们先使用静态库的方法来实现动态库的链接。
方法一:
拷贝到系统的默认路径下,一般指/usr/lib 这里不做演示,严重不推荐;
方法二 :
更改 LD_LIBRARY_PATH
cppexport LD_LIBRARY_PATH=/home/lyk/lesson8/friend/lib
LD_LIBRARY_PATH环境变量用于在程序加载运行期间查找动态链接库时指定除了系统默认路径之外的其他路径.注意,LD_LIBRARY_PATH中指定的路径会在系统默认路径之前进行查找;
添加好后,我们再次查看,发现路径已经指定好了 :
再次编译运行:
🌙 动静态库的加载
静态库不需要加载,静态库把代码拷贝到可执行程序里,直接决定了当加载的时候在内存里代码和数据可能存在多份,会比较浪费空间,把静态库中拷贝到程序中的代码区里:
动态库加上fPIC形成位置无关码,采用相对编址方案,在程序链接时对应库当中的偏移量添加到可执行程序,运行时一旦库加载进来,经过地址空间映射,把库映射到地址空间之后,库也就具备了起始地址,通过偏移地址和起始地址这样就可以找到访问的函数:
系统层面上会维护动态库的起始地址,直接建立页表与内存的映射,也就可以跳转访问了,所以动态库加载一次就可以被多个进程共同使用了。而静态库可能有多个程序用了C库,加载到内存时,内存里可能会存在100份重复的代码。而动态链接不会出现重复的代码,减少内存。
🌙 动静态库总结
静态库的特点:
- 静态库在可执行程序链接时就加入到可执行代码中,在物理上成为可执行程序的一部分;程序运行时将不再需要该静态库。
- 相对于动态库链接生成的程序,静态函相当于编译器将代码补充完整了,因此执行程序会大一些,但是运行起来相对快些;
- 静态库是牺牲了空间效率,换取了时间效率;
动态库的特点:
- 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在;
- 动态库只有在程序执行时, 那些需要的函数代码才被拷贝到内存中。这样就使可执行文件比较小, 节省磁盘空间;
- 由于运行时要去链接库会花费一定的时间,执行速度相对会慢一些;
- 动态库是牺牲了时间效率,换取了空间效率;
🌟结束语
今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。