1.动静态库的原理
源文件和头文件形成一个可执行文件需要经历以下的四个步骤:
- 预处理:展开头文件,宏替换,条件编译等,最后形成.i文件
- 编译:完成词法分析、语法分析、词义分析、符号汇总等。将代码翻译成汇编指令,最后形成.s文件
- 汇编:将汇编指令转换成二进制的机器码,最后形成.o文件
- 链接:将各个.o文件链接,最后形成可执行文件。
比如下面需要用eg1.c,eg2.c,eg3.c,eg4.c和mian.c形成可执行文件,需要以下的步骤:
如果其他项目也需要用到eg1.c,eg2.c,eg3.c,eg4.c文件,那么形成可执行文件的步骤是相同的。
这里eg1.c,eg2.c,eg3.c,eg4.c被反复的使用,如果将这些.c文件打包在一起,就形成了库,所以库本质上就是一堆.o文件的集合,往往这些文件集合,提供了大量的可调用方法。
2.动态库和静态库基础
静态库(.a):**在编译链接的时候直接将整个库的代码整合复制到可执行文件中,**程序运行的时候不再需要静态库,所以使用静态库的形成的文件往往比较大。
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。 动态库在编译的时候,在程序中只有一个指向的位置。
- 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
- 也就是说可执行文件在需要使用动态函数的时候,程序才会去读取函数库。
- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
3.动静态库的实现
3.1设计一个静态库
准备下面的几个文件
mymath.h和mymath.c
myprint.c和myprint.h
将上面的mymath.c和myprint.c编译生成对应的xxx.o文件,下面编写对应的Makfile
编写Makefile
ar是gnu归档工具,rc表示(replace and create)
最后生成了对应的静态库。
发布静态库
在使用库的时候,需要什么东西?
库文件和头文件。
执行make static发布静态库
3.2设计一个动态库
shared: 表示生成共享库格式
fPIC:产生位置无关码(position independent code)
库名规则:libxxx.so
gcc -fPIC -c sub.c add.c
gcc -shared -o libmymath.so *.o
-
fpIC将对于的文件加载到物理内存中,允许加载到物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
-
shared:将动态库发布,形成共享库的格式
编写对应的Makefile
生成对应的动态库并发布
4.动静态库的使用
上面我们实现了动静态并发布,下面讲解如何使用动静态库;
我们将动态库和静态库放在一个test目录下进行测试。
cp lib-static test/ -rf
cp lib-dyl test/ -rf
4.1静态库的使用
在使用静态库前,需要知道库里面有那些方法。
然而在包头文件时,我们依然发现找不到静态库中的头文件。
解决方案一:
指定头文件的路径,这个用法在实际工程中重要不大。
解决方案二:
头文件的搜索路径:
- 对于" "形式的头文件,是在当前(进程)路径查找。
- 对应<>形式的头文件,是在系统头文件下查找。
所以:我们可以考虑将自己的头文件和库文件拷贝到系统路径下。
- /usr/include这个路径下,存放了系统头文件
- /lib64路径下,存放了系统的动静态库
cp lib-static/include/* /usr/include #将静态库下的所有头文件拷贝到系统头文件路径下
cp lib-static/lib/* /lib64/ #将静态库下的所有库拷贝到系统路径下。
#要使用自己的库,-l需要指定第三方库的名字
gcc -o test main.c -lmymath
一个库的库名需要去掉前缀lib和后缀,静态库需要去掉lib和.a
注意:
方案二这种做法,容易污染操作系统的头文件和库,所以一般不推荐这种写法,除非是大佬写的库。
解决方案三:
指定路径:文件搜索路径
gcc ~~~ -I 头文件路径 -L 库文件路径 -l库名(去掉前缀lib和后缀)
4.2动态库的使用
方案一
将文件拷贝到系统路径,使用方法和静态库的使用方法相似。这种方法并不推荐
方案二:指定路径
使用方法:
gcc 依赖关系 -I 头文件路径 -L库文件理解 -l库文件名
为什么静态库生成的可执行程序可以成功执行,而动态库生成的无法执行?
程序和动态库是分开加载的。
在动静态库原理中我们提到过,链接静态库是直接拷贝静态库的内容,所以可执行程序不需要再依赖静态库
而动态库只是加一了一个指向,指向动态库的位置,而可执行程序执行是一个新的进程,所以需要重新链接动态库。
可执行程序如何链接动态库?
- 将动态库拷贝到/lib64/中
这种方法会污染系统库文件,所以不推荐
- 导入环境变量
程序在运行时,会在自己的环境变量**【LD_LIBRARY_PATH】**,中查找自己的动态库路径。
由于配置过Vim,所以与默认的有些不同。
接下来,可以将动态库的路径导入到LD_LIBRARY_PATH。
命令行式导入环境变量只在该shell中有效,重启后需要重新配置。
方案三:配置.conf文件
ldconfig 配置/etc/ld.so.conf.d/,ldconfig更新
动态库在运行时,还会扫描该路径【/etc/ld.so.conf.d/】下的配置文件。
所以我们可以创建一个动态库的配置文件,将动态库的路径加入配置文件中。
ldconfig 命令可以加载配置文件。然后程序就可以正常运行
ldd 文件:可以查看文件的链接信息。
方案四:在系统库中建立到动态库的软连接
由于动态库会自动搜索系统库文件,所以可以在系统库中建立导入的动态库的软连接
sudo ln -s 动态库绝对路径 /lib64/软连接名
4.3动态库的多进程共享原理
被加载到内存中的动态库,通过页表映射到了该进程地址空间中的共享区中,在运行时,则需要再次通过页表的映射,找到物理内存中的动态库。所以进程在运行时需要再次扫描物理内存。
而静态库在连接的过程中,代码则是被拷贝到可执行程序中,静态库则存放在每个进程的代码段,每个静态连接的进程都持有一份,所以静态库和进程可以分离。
如果有其他的进程动态链接了动态库:
所以动态链接有下面的特点:
- 动态库(.so),程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间 。
一些好玩的库
比如ncurse,Linux下的图形化界面库;boost,很经典的C++库。