文章目录
- 静态库
- 制作静态库
- 如何使用静态库
- 动态库
- 动态库的制作
- 动态库的使用
- 动态链接
库是给别人用的,所以库中一定不存在main函数。库一般会有lib前缀和后缀,去掉前缀和后缀才是库名。
静态库
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
库制作的本质就是把库中的源文件全部翻译成.o目标二进制文件,然后打包。
Linux中可以使用 gcc -c 选项将源文件翻译成.o文件。
制作静态库
制作静态库就只需要把所有的.h文件给用户,.h就相当于使用说明书,然后再把我们打包好的静态库,把他们放到一起给用户就可以了。
静态库打包可以用ar命令,可以使用Makefile。
这个Makefile就可以实现把打包库文件,然后使用make Output 就可以把头文件以及库文件一起打包放到一个文件,然后就只用打包交给用户就行了。
如何使用静态库
假设我们现在有一个刚才打包好的库,然后我们要使用它,应该怎么用?
这是库提供的头文件的内容。
这是源代码:
#include <stdio.h>
#include "mymath.h"
int main(){printf("3 + 2 = %d\n",add(3,2));printf("3 - 2 = %d\n",sub(3,2));return 0;
}
如果直接编译,肯定会报错
会提示找不到库提供的头文件,因为C语言在编译的时候,只会在系统特定的目录下和当前目录下查找头文件,库的头文件既不在当前目录下也不在系统的特定目录下当然找不到了,所以我们可以直接把头文件拷贝到系统目录下/user/include。
但是也可以使用gcc的-i选项指定搜索头文件的目录:
这时提示找不到库里面的方法,因为我们的是第三方库,需要告诉编译器我们要链接那个库,就需要gcc的-l选项:
这是有提示找不到库,因为编译器只会在默认路径(/usr/lib64)下搜索库,我们需要使用-L选项指定搜索库的路径。
这是就编译成功了,这就是使用静态库的大概流程,如果觉得太麻烦的话,最简单的办法就是把头文件和库都拷贝到系统的默认路径中。
动态库
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
动态库的制作
动态库依然是把.o文件打包,然后把.h和打包好的动态库一起交给用户,只不过动态库在形成.o是需要fPIC与位置无关码,打包.o文件直接使用gcc -shared 即可。
动态库的使用
动态库的使用和静态库一样,使用gcc命令,找不到头文件就用-i选项,-l指定需要链接的库,-L指定库的路径。
但是在运行的时候会有一些问题
我们可以使用ldd命令来查看一个可执行程序链接的动态库:
会发现找不到我们指定的库,这是因为我们虽然指定了库的路径,但是是给编译器指定的,程序编译成功成为可执行程序之后就和编译器没关系了,静态库没有这个问题是因为静态库是直接把自己需要的代码给自己拷贝了一份,而动态库是需要在运行的时候去动态库中找使用的函数,所以使用动态库链接的程序在运行的时候和库是强关联的。解决这个问题有很多种方法:
- 就是把头文件和库直接拷贝到系统的默认搜索路径中,即安装到系统。
- 使用软连接,可以在当前路径下或者库中建立软链接,因为动态库默认是会在当前路径下搜索的。
- 系统中有和环境变量LD_LIBRARY_PATH,可以把链接的动态库的路径添加到这个环境变量中,系统在搜索时会同时搜索这个环境变量中的路径。
- 直接更改系统的配置文件(etc/ld.so.conf.d/)
gcc默认是动态连接的,但是个别库,如果只提供静态库(.a),gcc也没有办法,只能局部性的把指定的静态库进行静态连接,其他库正常进行动态链接,如果使用-static,对于该可执行程序,所有的库就都必须静态链接,对于同一组库,如果动态库和静态库都提供,gcc默认使用动态库。
动态链接
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
使用了动态库的程序,在加载的时候,除了自己的代码数据需要的数组需要加载之外,使用到的动态库也是需要加载的,一般在虚拟地址空间中用映射的位置是,堆区和栈区之间的共享区。
程序被编译好之后,还没有被加载时它的内部其实就已经充满了各种各样的地址(函数,变量等),所谓的函数名变量名什么的在程序被编译好之后就都变成了地址,程序在编译的时候,对代码等进行编制,其实也是遵守我们虚拟地址空间的那一套的,所以虚拟地址空间不仅仅是OS的概念,更是一种标准。编译器在进行编译的时候,也要按照这样的规则编译可执行程序,这样才能在加载的时候,进行磁盘文件到内存后和进程空间进行映射。这种可执行程序为ELF格式的可执行程序。
因此程序内部的各种地址也是虚拟地址,程序在跳转到时候,需要执行到哪个虚拟地址的代码,根据页表直接找到对应的程序然后执行就可以了,因此CPU的每一次执行基本上都需要查表,这种虚拟地址也可以用基地址+偏移量的方式来表示,一般基地址是0,偏移量的范围[0,FFFFFFFF],这种地址成为逻辑地址,虚拟地址空间的这种模式为平坦模式。
我们知道位置有绝对位置和相对位置,同理编址的方式也有绝对编址和相对编址,而在我们代码中用到的库中的函数一般用的都是相对编制即相对于库的起始地址自己的位置,因为库在被加载的时候映射的位置不是固定的,每次映射的位置都是不一的,只用使用这样 库的名字 + 偏移量 的这种方式,才能保证库在加载是可以在共享区的任意位置进行加载。所以当库别加载之后地址就是确定的,然后用库的起始地址带起库的名字就可以很好的找到库中的任意函数。