库是指在我们的应用中,有一些公共代码是需要反复使用,就把这些代码编译为"库"文件;在链接步骤中,链接器将从库文件取得所需的代码,复制到生成的可执行文件中。
Linux中常见的库文件有两种,一种.a为后缀,为静态库,另一种以.so为后缀,为动态库。
一、静态库
可重定位目标文件以一种特定的方式打包成一个单独的文件,并且在链接生成可执行文件时,从这个单独的文件中“拷贝"它自己需要的内容到最终的可执行文件中。这个单独的文件,称为静态库。Linux中这类库的名字一般是libxxx.a。
1.创建步骤
将add.c sub.c创建成静态库文件:
- 创建add.c sub.c
#add.c
int add (int a, int b)
{
return a + b;
}
#sub.c
int sub (int a, int b)
{
return a - b;
}
- 对add.c sub.c编译成可重定位目标文件
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
- 利用ar工具创建静态库: ar rcs lib 库名.a所有可重定位目标文件
#生成静态库
ar rcs libmath.a add.o sub.o
2.静态库的使用
制作使用libmath.a静态库的程序
- 创建main.c
#include<stdio.h>
int add(int a,int b);
int sub(int a,int b);
int main(int argc, char* argv[])
{
int a=5,int b=3;
printf("a+b=%d\n",add(a,b));
printf("a-b=%d\n",sub(a,b));
return 0;
}
- 编译main.c
编译时要将静态库libmath.a加上gcc选项:
- l:指定库名(库的文件名为libxxx.a,库名为xxx)
- L:指定库路径
- static:使用静态链接
gcc -static main.c -l math -L ./
#注意:要先运行main.c后运行静态库文件
./a.out
特别注意,必须把-l math 放在后面。放在最后时它是这样的一个解析过程:
- 链接器从左往右扫描可重定位目标文件和静态库
- 扫描main.c时,发现两个未解析的符号add和sub,记住这两个未解析的符号
- 扫描libmath.a,找到了前面未解析的符号,因此提取相关代码
- 最终没有任何未解析的符号,编译链接完成
那如果将-l math放在前面,又是怎样的情况呢?
- 链接器从左往右扫描可重定位目标文件和静态库
- 扫描libmath.a,由于前面没有任何未解析的符号,因此不会提取任何代码
- 扫描main.c,发现未解析的符号add和sub
- 扫描结束,还有两个个未解析的符号,因此编译链接报错
生成可执行文件大小:
生成的可执行文件大小为826k
由于最终生成的可执行文件中已经包含了add和sub相关的二进制代码,因此这个可执行文件在一个没有libmath.a的Linux系统中也能正常运行。
二、动态库
动态库和静态库类似,但是它并不在链接时将需要的二进制代码都“拷贝"到可执行文件中,而是仅仅“拷贝”一些重定位和符号表信息,这些信息可以在程序运行时完成真正的链接过程。Linux中这类库的名字一般是libxxx.so。(shared object)
1.创建步骤
//先编译成可重定位目标文件(生成与位置无关的代码-fPIC)
gcc -c add.c -o add.o -fPIC
gcc -c sub.c -o sub.o -fPIC
//使用gcc -shared 制作动态库
gcc -shared -o libmath.so add.o sub.o
-fPC作用:生成与位置无关的代码
2.动态库的使用
通常我们编译的程序默认就是使用动态链接
gcc main.c -o main -l math -L ./
通过动态库链接的程序只有8.5k.
通过 ldd命令来观察可执行文件链接了哪些动态库:
因为没有把 libmath.so 中的二进制代码“拷贝"可执行文件中,程序在其他没有上面的动态库时,将无法正常运行。
3.找不到动态库
运行可以执行程序./main出错!!! ldd main --> "not found"
链接器:工作于链接阶段,工作时需要-l和L
动态链接器:工作于程序运行阶段,工作时需要提供动态库所在目录位置。
原因:没有提供动态库的位置
解决方式:
- 通过环境变量(临时生效): export LD_LIBRARY_PATH=动态库路径
- 环境变量写入配置文件~/.bashrc(使用绝对路径),生效方法: .~/.bashrc或source ~/.bashrc 或 重启终端
- 拷贝自定义动态库到lib(标准C库所在目录位置)(不推荐)
【注】只是临时生效。关闭终端再重启时使用./main还会报错。如果想要一直生效需要写在配置文件中。
动态库绝对路径写到/etc/ld.so.conf配置文件中,生效方法: sudo ldconfig
三、静态库和动态库的区别
静态库被使用目标代码最终和可执行文件在一起(它只会有自己用到的),而动态库与它相反,它的目标代码在运行加载时链接。正是由于这个区别,会导致下面所介绍的这些区别。
1.可执行文件大小
静态链接的可执行文件要比动态链接的可执行文件要大得多,因为它将需要用到的代码从二进制文件中“拷贝”了一份,而动态库仅仅是复制了一些重定位和符号表信息。
2.扩展性与兼容性
如果静态库中某个函数的实现变了,那么可执行文件必须重新编译,而对于动态链接生成的可执行文件,只需要更新动态库本身即可,不需要重新编译可执行文件。正因如此,使用动态库的程序方便升级和部署。
3.依赖原库文件
静态链接的可执行文件不需要依赖其他的内容即可运行,而动态链接的可执行文件必须依赖动态库的存在。所以如果你在安装一些软件的时候,提示某个动态库不存在的时候也就不奇怪了。
即便如此,系统中一般存在一些大量公用的库,所以使用动态库并不会有什么问题。
4.加载速度
由于静态库在链接时就和可执行文件在一块了,而动态库在加载或者运行时才链接,因此,对于同样的程序,静态链接的要比动态链接加载更快。所以选择静态库还是动态库是空间和时间的考量。但是通常来说,牺牲这点性能来换取程序在空间上的节省和部署的灵活性时值得的。
5.库的制作复杂度
相对来讲,动态库的处理要比静态库要复杂,例如,如何在运行时确定地址?多个进程如何共享一个动态库?当然,这些我们不需要关注。另外动态库版本的管理也是一项技术活。