Linux 动静态库详解
目录
前言:
1、什么是动静态库
2、为什么要有动静态库
一、初识动静态库
1、我们平时写的代码中的动静态库
2、动静态库的命名标准
3、如何使用动态库或静态库
二、如何制作自己的库
三、如何使用自己的静态库
1、打包自己的静态库
2、使用自己的静态库
指定库的路径
四、如何制作和使用自己的动态库
1、打包自己的动态库
2、使用自己的动态库
编辑
1、拷贝到系统目录
2、更改 LD_LIBRARY_PATH环境变量
3:进行软链接
五、补充
1 gcc默认优先链接动态库
2 动态库可分批加载到内存
3 动静态库的区别
4 动态库的意义
前言:
1、什么是动静态库
动静态库的本质是可执行程序的“半成品”,我们都知道,一堆源文件和头文件最终变成一个可执行程序需要经历以下四个步骤:
预处理:完成头文件展开、去注释、宏替换、条件编译等,最终形成xxx.i文件。
编译: 完成词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成xxx.s文件。
汇编: 将汇编指令转换成二进制指令,最终形成xxx.o文件。
链接: 将生成的各个xxx.o文件进行链接,最终形成可执行程序。
- 静态库:在编译时,库的代码被直接复制到可执行文件中,程序运行时不依赖外部库文件。
- 动态库:在程序运行时加载库的代码,多个程序共享同一份库的实现,节省内存和磁盘空间
- 在 Linux 下,动态链接库(shared object file,共享对象文件)的文件后缀为 .so。使用动态链接库的优点是:程序的可执行文件更小,便于程序的模块化以及更新,同时,有效内存的使用效率更高
关于以上各类文件后缀的含义:
.o文件是目标文件,它是源代码编译后生成的二进制文件。.o是object的缩写,表示这个文件包含了目标代码,即机器语言代码。
.a文件是静态库文件,它是由多个目标文件打包而成的。.a是archive的缩写,表示这个文件是一个归档文件,包含了多个目标文件。
.so文件是动态库文件,它也是由多个目标文件组成的。.so是shared object的缩写,表示这个文件是一个共享对象,可以在程序运行时被载入。
.i文件是预处理后的C或C++源代码文件。预处理器会处理源代码中的宏定义、条件编译和头文件包含等指令,生成一个.i文件,其中包含了预处理后的源代码。
.s文件是汇编语言源代码文件。它包含了用汇编语言编写的程序代码,可以通过汇编器转换为机器语言代码。
.out文件是可执行文件,它包含了可以直接在计算机上运行的机器语言代码。.out是output的缩写,表示这个文件是编译器输出的结果。
.exe文件是Windows操作系统下的可执行文件。它包含了可以直接在Windows操作系统上运行的机器语言代码。.exe是executable的缩写,表示这个文件是可执行的。与.out文件类似,.exe文件也是编译器输出的结果,它可以直接在计算机上运行。不过,.exe文件只能在Windows操作系统上运行,而不能在其他操作系统(如Linux)上运行。
2、为什么要有动静态库
当有多个不同的源文件中的main函数调用这些功能函数时,每次都要重新对这几个函数重复预处理、编译、汇编操作,各自生成.o文件,然后再和调用功能函数的源文件(一般是main函数)生成的.o,最后才生成可执行程序。
这样会有很多重复的操作,所以一般将这些常用的函数所在的.cpp文件预处理、编译、汇编生成的多个.o文件打包在一起,称之为库。有动静态库后可以避免这些重复的操作。
总结
- 代码复用:避免重复编写通用功能模块,提高开发效率。
- 模块化:方便代码维护与更新(动态库更新无需重新编译程序)。
- 资源优化:静态库保障程序独立性,动态库减少冗余代码占用
一、初识动静态库
1、我们平时写的代码中的动静态库
在下面代码中,我们使用了C的标准库
在生成可执行文件后,可以通过ldd命令来查看可执行程序依赖的库文件:
ldd 文件名
再通过 file /lib64/libc-2.17.so 查看该文件的文件类型
可以看见库文件是 shared object,那么它是一种特殊的目标文件,可以在程序运行时被加载(链接)进来。也就是说 libc-2.17.so 是一个动态链接的动态库。
2、动静态库的命名标准
- 静态库:Linux 下后缀为
.a
(如libmylib.a
),Windows 为.lib
36。- 动态库:Linux 下后缀为
.so
(如libmylib.so
),Windows 为.dll
35。- 命名规则一般为
lib<name>.<后缀>
,链接时需省略lib
前缀(如-lmylib
)。
例如: libc-2.17.so 动态库,他的前缀为lib 后缀为 .so ,因此 libc-2.17.so 的文件名为c。
3、如何使用动态库或静态库
默认情况下,gcc/g++ 采用动态链接的方式链接第三方库。但是 gcc/g++ 提供了一个 -static
参数,可以改变默认链接方式。如:
gcc -static test.c -o test
当使用静态库的时候,文件的体积会明显增大
文件 a 是静态链接,sa 是动态链接。可明显看见 静态链接比动态链接 文件体积大的多 。
二、如何制作自己的库
制作自己的库就是生成 .o 文件
先将print.c(函数实现文件)编译成 .o 文件
再将main.c(main函数文件)编译成.o 文件
然后,将main.o和其他.o文件链接以后生成的文件就是可执行程序:
通过上面的例子我们可知,需要将生成的所有目标文件和main.o文件链接就能生成可执行程序。
三、如何使用自己的静态库
1、打包自己的静态库
除了main.o之外的.o文件都太分散了,用起来很麻烦(当然可以通过Makefile简化步骤),给别人使用也不太方便,还容易缺失,所以将它们打包。而将目标文件打包的结果就是一个静态库。
使用ar
指令将所有目标文件打包为静态库。
常用参数:
-r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。
-c(create):建立静态库文件。
-t:列出静态库中的文件。
-v(verbose):显示详细的信息。
语法:ar [选项] [库名] [依赖文件]
ar -rc libtest.a print.o
注意,我这里只有一个.o(实现文件),如果有多个,都要一起打包,main函数不要打包进去。
到此我们还要将打包成的静态库需要和头文件组织起来。
组织静态库和头文件的方法有很多种。一种常见的方法是将静态库文件(.a文件)和头文件放在同一个目录下。在使用静态库时,需要在程序中包含对应的头文件,并在编译时指定静态库的位置。这样,编译器就能够找到静态库中的函数和变量,并将它们链接到程序中。
例如,将所有的头文件(.h)放在一个名为include
的目录下,将生成的静态库文件(.a)放在一个名为lib
的目录下。然后将这两个目录都放在名为libtest
的目录下,这个libtest
就可以作为一个第三方库被使用。
我们可以使用Makefile来打包
libtest.a : Add.o Print.o # 静态库依赖的目标文件ar -rc libtest.a Add.o Print.o # 打包
Add.o : Add.c # .o文件依赖的源文件gcc -c Add.c -o Add.o
Print.o : Print.cgcc -c Print.c -o Print.o.PHONY : mylib # 组织头文件和库文件
mylib:mkdir -p mylib/libmkdir -p mylib/includecp -rf *.h mylib/includecp -rf *.a mylib/lib.PHONY : clean
clean : rm -rf *.o mylib libtest.a
Makefile:打包 生成目标 .o 文件
make mylib :组织静态库文件和头文件,生成mylib
使用tree命令来查看mylib文件内
2、使用自己的静态库
方法一:使用选项
指定库的路径
gcc 编译 main.c 链接库时,需要由以下三个选项定位文件:
-I
:指定头文件搜索路径。-L
:指定库文件搜索路径。-l
:指明需要链接库文件路径下的哪一个库。
我们只要显式地给 gcc 指明第三方库的路径即可完成链接:
解释说明:
因为编译器不知道你所包含的头文件add.h在哪里,所以需要指定头文件的搜索路径。
因为头文件add.h当中只有my_add函数的声明,并没有该函数的定义,所以还需要指定所要链接库文件的搜索路径。
库文件名去掉前缀lib,再去掉后缀.so或者.a及其后面的版本号,剩下的就是这个库的名字。
-I,-L,-l这三个选项后面可以加空格,也可以不加空格。
方法二:把头文件和库文件拷贝到系统路径下
sudo cp mathlib/include/* /usr/include/
sudo cp mathlib/lib/libcal.a /lib64/
该方法也要指明要使用哪个库,而且会污染系列库,不建议使用
四、如何制作和使用自己的动态库
1、打包自己的动态库
我们与上面相同使用Makefile来生成
第一步:让所有源文件生成对应的目标文件,此时用源文件生成目标文件时需要携带-fPIC
选项:
使用make来生成.
-fPIC
(position independent code):产生位置无关码。此处不做介绍
此步骤生成的是 .o 文件。
第二步:使用-shared选项将所有目标文件打包为动态库,与生成静态库不同的是,生成动态库时我们不必使用ar命令,我们只需使用gcc的-shared选项即可。
使用make来生成
gcc -shared -o libcal.so print.o
此处生成的是动态库
第三步:将头文件和生成的动态库组织起来
使用make output来将其组织起来
与生成静态库时一样,为了方便别人使用,在这里我们可以将add.h
和sub.h
这两个头文件放到一个名为include的目录下,将生成的动态库文件libcal.so
放到一个名为lib的目录下,然后将这两个目录都放到mlib下,此时就可以将mlib给别人使用了。
2、使用自己的动态库
与静态库一致生成可执行文件
gcc main.c -I./mylib/include -L./mylib/lib -lmystdio
到此与静态库内容大差不差。
当我们运行时发现这个可执行程序不能运行
我们使用ldd命令查询
发现libtest.so => not found,这说明系统无法找到动态库文件。
在Linux下,有以下几种使用第三方动态库的方法(解决以上问题):
1、拷贝到系统目录
类似静态库的操作:
sudo cp mylib/lib/libtest.so /lib64
现在这个动态库就被找到了
2、更改 LD_LIBRARY_PATH环境变量
LD_LIBRARY_PATH
是程序运行动态查找库时所要搜索的路径,我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH
环境变量中,告诉系统程序依赖的动态库所在的路径:注意要用:
隔开,否则会覆盖原来的环境变量。但是这个方法是临时的,因为这个环境变量是内存级别的环境变量,机器会在下次登录时清理。export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/xy/Linux/libtest/libtest/mylib/lib
3:进行软链接
使用如下代码进行软链接:
ln -s /home/cl/BasicIO/testlib/project/mlib/lib/libcal.so libmycal.so
这句代码就是将文件夹中的libcal.so软链接到当前目录下,因此当前路径就多了一个libcal.so
,执行可执行程序时,虽然他不会进入当前目录的文件夹里面找,但是会在当前目录下查找是否有动态库,因此我们软链接到当前目录下,就可以执行了。
这样就可以在动态链接的时候找到了
五、补充
.加载静态库的程序运行速度相对较快
1、gcc默认优先链接动态库
如果同时存在动态库和静态库文件,gcc会优先链接动态库。如果你想强制gcc链接静态库,可以直接指定静态库的全称,或者使用-static选项。你也可以使用-Bdynamic和-Bstatic选项在命令行中切换首选项。
2 动态库可分批加载到内存
动态库在程序运行时才被加载,它和可执行文件是分开的,只是可执行文件在运行的某个时期调用了它。动态库可以实现进程之间资源共享,有一份就行。
可执行程序先加载,代码区的代码运行到动态库的方法时,可以先跳转到共享区,而共享区是保存了页表和动态库的映射关系的,所以使用完动态库的方法以后,再返回到代码区继续执行即可。由此可见,静态库是在可执行程序自己的进程地址空间中跳转的。
3 动静态库的区别
静态库和动态库的最大区别是,静态库链接的时候把库直接加载到程序中(也就是直接拷贝一整份库),而动态库链接的时候,它只是保留接口,将动态库与程序代码独立。这样就可以提高代码的可复用度和降低程序的耦合度。动态库相对于静态库有便于更新拓展、减小体积等诸多优势。
但这不意味着静态库是一无是处的。
当你更希望简单易用,尽量免除后顾之忧,那就首选静态(隐式)连接。静态库在链接时就和可执行文件在一块了,因此,对于同样的程序,静态链接的要比动态链接加载更快。所以选择静态库还是动态库是空间和时间的考量。但是通常来说,牺牲这点性能来换取程序在空间上的节省和部署的灵活性时值得的。
4 动态库的意义
动态库把对一些库函数的链接载入推迟到程序运行的时期,可以实现进程之间的资源共享,将一些程序升级变得简单。库是别人写好的现有的、成熟的、可以复用的代码,用户可以按照说明使用库。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
可以减少代码冗余,只需要一份即可。分批加载后,只要建立相对地址关系即可。如果有很多个进程都要用同一个库,只要建立映射关系即可。只要加载一次,就可以在内存中被多份代码复用。
区继续执行即可。由此可见,静态库是在可执行程序自己的进程地址空间中跳转的。
------------------------------------------------------------------------------------------------------------------------------