什么是动静态库
库文件主要分为动态库和静态库
在Linux中,以
.so为后缀的是动态库,以.a为后缀的是静态库。
在Window中,以.dll为后缀的是动态库,以.lib为后缀的是静态库。
静态库在程序编译时链接到可执行文件中,本质上是将库中的代码和数据复制到可执行文件中,所以静态库代码就成了可执行文件的一部分了。这就意味着多个程序使用同一个静态库时,库代码会被重复复制,从而导致程序体积太大。
动态库是在运行时由操作系统加载到内存中的库,多个程序可以共享一个动态库,所以程序体积较小,库更新后,只需要替换动态库文件,不需要重新编译。
动静态库对比:
静态库的优点:
- 独立性强,因为可执行文件中包含库代码
- 无需动态加载,所以运行速度较快
静态库的缺点:
- 生成程序的体积较大
- 更新后需要重新编译依赖该库的程序
动态库的优点:
- 生成程序的体积较小,库代码没有在可执行文件中
- 更新后无需重新编译依赖该库的程序,只需要替换库文件
动态库的缺点:
- 依赖外部环境,因为可执行文件里没有包含库代码
- 加载动态库需要一定开销,所以运行速度较慢
动静态库的原理
将一个源文件转变为一个可执行文件,通常需要经历以下四个主要步骤:
- 预处理: 完成头文件展开、去注释、宏替换、条件编译等,最终形成xxx.i文件。
- 编译: 完成词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成xxx.s文件。
- 汇编: 将汇编指令转换成二进制指令,最终形成xxx.o文件。
- 链接: 将生成的各个xxx.o文件进行链接,最终形成可执行程序。
我举个例子:
比如我们现在有三个文件test1.c,test2.c,test3.c,以及main1.c这四个.c文件,经过预处理,编译,汇编之后分别生成test1.o,test2.o,test3.o,以及main1.o这四个.o文件。最后经过生成a.out的可执行文件。

如果再有一个main2.c的文件也依赖与test1.c,test2.c,test3.c这三个文件,那我们直接将这三个文件汇编之后分别生成test1.o,test2.o,test3.o打包,再拿给main2.c,这个包就是我们所说的库。
所以动静态库的本质其实是一堆xxx.o文件的集合。对于库的使用,只需要提供头文件让使用者了解具体功能的作用。在编译程序时,通过链接指定的库来实现对库中功能的调用。
Linux中的库文件
我们用ldd 文件名可以查看一个可执行程序所依赖的库文件。

通过file指令我们发现,gcc/g++编译器默认都是动态链接,如果想使用静态链接,需要在后面加一个-static,记得先安装静态库(指令:sudo apt install libc6-dev libstdc++-static )


我们需要注意:动静态库真实文件名需要去掉前缀lib,再去掉后缀.so或者.a及其后面的版本号,比如说libc-2.17.so就是C语言的标准库,其名为:c-2.17。
动静态库的打包和使用
静态库的打包和使用
我们搞个简单的例子:
add.h
#pragma once
extern int add(int x, int y);
add.c
#include "add.h"
int add(int x, int y)
{
return x + y;
}
sub.h
#pragma once
extern int sub(int x, int y);
sub.c
#include "sub.h"
int sub(int x, int y)
{
return x - y;
}
想要把上面4个文件打包成静态库一共需要3步:
1. 将源文件生成对应的.o文件。

2. 使用ar指令打包成对应的静态库。
ar 选项 库名 打包文件名
-r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件
-c(create):建立静态库文件

3. 将头文件和生成的静态库发布到mathlib目录
当别人要使用我们打包好的库时,通常需要给予对方两个文件:
一个文件夹用于存放头文件集合。比如,可以将
add.h和sub.h这两个头文件放置在名为include的目录下。
另一个文件夹用于存放所有的库文件。例如,把生成的静态库文件libmath.a放到名为lib的目录下。
然后再将include和lib发布到mathlib目录里,其他人就可以通过我打包好的mathlib用我的库了。


我们也可以用指令实现这三步
#打包静态库
libmath.a:add.o sub.o
ar -rc libmath.a $^
#展开所以.c文件生成对应的.o文件
%.o:%.c
gcc -c $^
#维护代码一致性,及时删除旧饿残留文件
.PHONY:clean
clean:
rm -rf ./*.o mathlib
#发布库
.PHONY:output
output:
mkdir -p mathlib/include
mkdir -p mathlib/lib
cp ./*.h mathlib/include
mv ./*.a mathlib/lib
接下来演示如何使用静态库:
在使用我们自己打包的静态库时,gcc编译时需要知道头文件、库文件、静态库名
-I:指定头文件搜索路径。
-L:指定库文件搜索路径。
-l:指明需要链接库文件路径下的哪一个库。
在程序执行时,编译器不知道我们头文件以及库的具体位置,而且链接库中可能存在不同的库,所以我们要给编译器明确指出。
我们整个main.c,main.c中使用静态库的add函数
#include<stdio.h>
#include"add.h"
int main()
{
int a=1;
int b=2;
int ret=add(a,b);
printf("%d\n",ret);
return 0;
}

我这里提一嘴,如果要更新静态库文件,我们得重新编译一下程序(以改正.c和.h的函数名统一为例):
# 查看库文件中的符号名称(确认是否已更新)
nm ./mathlib/lib/libmath.a | grep 'add'
# 如果输出仍然是 my_add,说明需要:
# 1. 重新编译 add.c
gcc -c add.c -I./mathlib/include
# 2. 更新库文件
ar rcs ./mathlib/lib/libmath.a add.o
# 3. 重新编译 main.c
gcc main.c -I./mathlib/include -L./mathlib/lib -lmath -o main
为啥gcc编译时没有带-I、-L、-l这三个选项呢?
其实很简单,因为我们之前使用的库都默认在系统的路径下: 编译器能准确识别这些存在于配置文件中的路径。其实如果为了方便我们也可以将头文件和库文件拷贝到系统路径/usr/include,/lib.64下:
sudo cp mathlib/include/* /usr/include/
sudo cp mathlib/lib/* /lib.64/
这个时候再编译只需要带-l选项,指明链接库文件下具体哪个库。
但是不建议这么做,可能会对系统文件造成污染。
动态库的打包和使用
动态库的演示我还是用的静态库的四个文件。
1. 将源文件生成对应的.o文件。
静态库和动态库不同,需要带-fPIC,因为动态库是运行时才加载到内存的。
<font style="color:rgb(28, 31, 35);">-fPIC(position independent code)</font>即产生位置无关码,作用于编译阶段,其目的是告诉编译器生成与位置无关的代码。在这种情况下,所产生的代码中不存在绝对地址,全部采用相对地址(起始位置加上偏移量)。这使得动态库被加载器加载到内存的任意位置时都能够正确执行。倘若不添加该选项,代码中使用的库函数在执行时会尝试调到对应位置执行,但此时可能会因该位置被其他动态库所占用而找不到该函数。
2. 使用-shared指令打包成对应的动态库。
生成动态库不用ar指令,带-shared选项就行。

3. 将头文件和生成的静态库发布到mathlib目录
和静态库一样
一个文件夹用于存放头文件集合。比如,可以将add.h和sub.h这两个头文件放置在名为include的目录下。
另一个文件夹用于存放所有的库文件。例如,把生成的静态库文件libmath.so放到名为lib的目录下。
然后再将include和lib发布到mathlib1目录里,其他人就可以通过我打包好的mathlib1用我的库了。
为了方便也可以用Makefile。
#打包动态库
libmath.a:add.o sub.o
gcc -shared -o $@ $^
#展开所以.c文件生成对应的.o文件
%.o:%.c
gcc -fPIC -c $^
#维护代码一致性,及时删除旧饿残留文件
.PHONY:clean
clean:
rm -rf ./*.o mathlib1
#发布库
.PHONY:output
output:
mkdir -p mathlib1/include
mkdir -p mathlib1/lib
cp ./*.h mathlib1/include
mv ./*.so mathlib1/lib
接下来演示如何使用动态库:
和静态库一样要三个选项
-I:指定头文件搜索路径。
-L:指定库文件搜索路径。
-l:指明需要链接库文件路径下的哪一个库。
怎么不行呢?为啥加了三个选项还不行?找不到对应的动态库

这是由于我们使用
-I、-L、-l这三个选项仅仅是在编译期间向编译器告知我们所使用的头文件和库文件的具体位置以及具体的库名。然而,当可执行程序生成后,它便与编译器不再有直接关系。所以,该可执行程序运行起来时,操作系统仍找不到该可执行程序所依赖的动态库。
通常有三种办法:
1. 第一种就是将库文件拷贝到系统共享的库路径下。
sudo cp mathlib/lib/libmath.so /lib64
这种方法不推荐,容易对系统文件造成污染。
2. 第二种就是更改环境变量LD_LIBRARY_PATH。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/lyn/lesson8/library_file/mathlib1/lib
(对应动态库所在路径)
LD_LIBRARY_PATH是程序运行动态查找库时所要搜索的路径,我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH环境变量当中,程序运行起来时就能找到对应的路径下的动态库。
第二种方法是临时的,关闭终端就没了。
3. 配置.conf/文件
在系统中,/etc/ld.so.conf.d/是用于搜索动态库的路径。此路径下存放的全是后缀为.conf的配置文件,这些配置文件中所存放的内容都是动态库的路径。
因此,若将自己库文件的路径也放置在该路径下,那么当可执行程序运行时,系统就能够找到我们的库文件。并且这种行为是永久的,并不会随重启而改变。
首先我们将对应的库文件所在地址写入一个.conf文件中,然后将其导入/etc/ld.so.conf.d/路径,最后使用指令ldconfig更新一下配置文件,最后我们就能执行我们的可执行文件了。


2064

被折叠的 条评论
为什么被折叠?



