动态库和静态库
动静态库基本概念
一、动静态库的基本原理
首先我们先了解一下一个代码转换成程序需要的步骤都有哪些:
- 预处理: 将代码转换成test.i代码 ,还是c语言代码
- 编译: 将test.i转换成汇编语言代码,test.s
- 汇编: 将test.s转换成可重定向的二进制文件(目标文件),test.o
- 链接: 将这些二进制文件链接起来转换成真正意义上的可执行文件。
那么库是怎么形成的呢??
当我们将代码从test.c变为test.o的时候,我们将这些.o文件集合起来,所以可以库的本质就是一堆.o文件的集合,注意这里不包括main函数,但是包含大量的方法。
二. 认识动静态库
-
ldd命令
ldd a.out
该命令可以查看一个可执行程序应用的库文件,如下图:
注意:ldd只能查动态库,静态库已经拷贝进了程序里。 -
库的命名
在linux中以.so结尾的叫做动态库,以.a结尾的叫做静态库,而在windows中以.dll结尾的为动态库,以.lib结尾的为静态库
- 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
- 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
- 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文 件的整个机器码
- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚 拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
静态库
我们所集合 .o 后缀的文件为第三方库,一般是以 .a 为扩展名的文件。
给用户.h 和.o就能被使用,无需放入 .c 源代码
-
生成静态库
使用 ar -rc 命令打包静态库, 将xx1.o与xx2.o等目标文件包装成一个libxxx.a静态库#首先需要有对应的 add.o sub.o 文件,如果用makefile,则需要先make将对应的文件进行编译,再执行下面的语句进行打包 ar -rc libmymath.a add.o sub.o #ar是gnu归档工具,rc表示(replace and create)
-
链接使用静态库
正确使用 -i(需要大写,这里为了方便区分) 、-l(小写) 、-L 命令链接静态库
我们刚刚创建并且发布了一个静态库, 但我们并没有把它放到对应的gcc默认的系统搜索路径中, 也就是说它完全是一个第三方库, 我们在gcc编译时需要手动添加搜索路径
gcc编译命令: gcc main.c -o mybin -I(大写i) 头文件路径 -L 库路径 -l(小写L)库名gcc test.c -I mymath_lib/include -lmymath -L mymath_lib/lib # -I[新增头文件搜索路径] -l[链接的库名] -L[链接库的文件位置] # -I 新增头文件搜索路径 # -l 指明链接库的名称 例: libmymath.a 的名称是 mymath # -L 新增库文件搜索路径 .o
-
发布静态库
发布库: 形成好静态库之后, 创建一个目录output, 在output中创建两个目录, 分别是include存放头文件, lib存放刚刚包装好的库mkdir -p mymath_lib/inlude mkdir -p mymath_lib/lib cp -f *.h mymath_lib/include cp -f *.a mymath_lib/lib
-
makefile的编写
static-lib=libmymath.a #库真正的名字是mymath #static-lib 是声明变量 $(static-lib):Add.o Div.o Mul.o Sub.o ar -rc $@ $^ #$^ 代表上面所有的文件 $@ 代表1依赖关系 %.o:%.c gcc -c $@ $< .PHONY:output #打包为库 output: mkdir -p mymath_lib/inlude mkdir -p mymath_lib/lib cp -f *.h mymath_lib/include cp -f *.a mymath_lib/lib .PHONY:clean clean: rm -rf *.o *.a mymath_lib test #这里需要rf强制递归删除
动态库
在liux系统中,动态链接文件称为动态共享对象(DSO,Dynamic shared objects),简称共享对象,一般是以.so为扩展名的文件。在windows系统中,则称为动态链接库(Dynamic linking library),很多以 .dll 为扩展名。
我们所集合 .o 后缀的文件为第三方库,给用户.h 和.o就能被使用,无需放入 .c 源代码
-
生成动态库
使用命令gcc -shared xxx1.o xxx2.o -o lib动态库名称.so, 生成动态库gcc -fPIC -c Div.c Sub.c Mul.c Add.c #编译.c 文件,生成.o 并且与位置无关 gcc -shared Div.o Sub.o Mul.o Add.o -o libmymath.so # gcc -shared Div.o Sub.o Mul.o Add.o [-o libmymath.so] #等价于 #gcc -shared [-o libmymath.so] Div.o Sub.o Mul.o Add.o gcc -shared -o libmymath.so Div.o Sub.o Mul.o Add.o
-
链接使用动态库
代码编译:
gcc test.c -I mymath_lib/include -lmymath -L mymath_lib/lib # [-I mymath_lib/include]表示将/home/include目录作为第一个寻找头文件的目录,若找不到需要的头文件,系统会自动到/usr/include找,若还找不到就会到usr/local/include中找
-
法1:直接安装到系统中,即将对应的头文件以及库拷贝至系统的指定目录下
-
法2:通过软链接,查找动态库。
前提是已经存在可执行程序 a.out ,那么在与 a.out 的同文件夹内,建立一个软链接,并且该软链接必须与动态库同名,这样这种方法也能使 a.out 链接到动态库
如果库在当前可执行程序的路径下,通过建立软链接,动态库可以被找到,可执行程序也能运行 -
法3:使用环境变量的方式
改变LD_LIBRARY_PATH环境变量(系统默认库加载时的/运行时搜索路径), 将动态库所在路径添加到该环境变量中, 但这是一次性的, 下次登陆环境变量就会恢复 -
法4:修改系统关于动态库的配置文件
/etc/ld.so.conf.d/
更改配置文件, 目录:/etc/ld.so.conf.d/, 更改好配置文件之后使用命令ldconfig更新(刷新缓冲)即可永久化生效
改变配置文件, 本质: 在/etc/ld.so.conf.d 目录下创建一个新的.conf文件(sudo权限创建), 然后在新创建的文件中写入动态库路径,再用ldconfig刷新一下(本质是在刷新缓冲区) 。
-
-
发布动态库库
发布库: 形成好静态库之后, 创建一个目录output, 在output中创建两个目录, 分别是include存放头文件, lib存放刚刚包装好的库mkdir -p mymath_lib/inlude mkdir -p mymath_lib/lib cp -f *.h mymath_lib/include cp -f *.so mymath_lib/lib
-
makefile的编写
dy-lib=libmymath.so #定义变量 $(dy-lib):Add.o Div.o Mul.o Sub.o gcc -shared -o $@ $^ # [-shared] 表明产生共享库 # 注意: -o:指定生成可执行文件的名称,如果不使用-o选项,则会生成默认可执行文件a.out %.o:%.c gcc -fPIC -c $< # [fPIC]: 与位置无关码。 # linux下编译共享库时,必须加上-fPIC参数,否则在链接时会有错误提示 # -c: 只编译、汇编。不链接,只生成目标文件(.o)。 .PHONY:output output: mkdir -p mymath_lib/include mkdir -p mymath_lib/lib cp -f *.h mymath_lib/include cp -f *.so mymath_lib/lib .PHONY:clean clean: rm -rf *.o *.so mymath_lib
注意: [ -fPIC ]: 表明使用地址无关代码。PIC: position independent code.fpic 。共享对象可能会被不同的进程加载到不同的位置上,如果共享对象中的指令使用了绝对地址,外部模块地址,那么在共享对象被加载时就必须根据相关模块的加载位置对这个地址做调整,也就是修改这些地址,让它在对应进程中能正确访问。
动静态库的区别
- 静态库是函数和数据编译进一个二进制文件里面(.lib文件),在使用静态库链接成可执行程序的时候,链接器会复制静态库内的函数和数据进可执行程序里面(.EXE文件),所以在加载库的时候不需要加载相应的库函数
- 动态库往往是提供两个文件,一个是引入库文件(.lib)和一个DLL(.dll)文件,但是引入库文件(.lib)只是包含DLL文件的导出函数以及函数的变量名符号,而DLL文件里包含了所有的函数以及数据,假如调用动态库是在编译到可执行程序的时候才会调用没有数据以及函数的复制,所以在发产品的时候需要加上相应的动态库
动态链接库加载函数的两种方法
-
第一种是静态链接方式,在这种方式下,动态链接库中的所有数据代码都将拷贝到调用程序的代码空间中去,此时它和调用程序本身的函数没有什么区别;
-
第二种是动态链接方式,在这种方式下,动态链接库中的数据代码是在需要的时候才拷贝到内存中去的;
两种方式都可以将外部(动态链接库中的)代码资源提供给调用者使用,但前者是全局的,虽随时可以使用,但会增加内存;而后者是局部的,虽节省了内存,但会增加调用时间。
动态库的地址映射关系
磁盘中的可执行程序(ELF)会在特定的位置记录下该程序的入口地址(main),随后可以根据入口地址和偏移量进行调用函数,此时可执行程序的虚拟地址已经存在
动态库采用相对编址的方式,程序在调用函数时,只需要记录是哪一个库,且函数相对 库在物理内存起始位置的相对偏移量 即可,所以动态库具有与地址无关性,其在编译时是相对编址的
另外程序在地址空间中采用虚拟地址的方式,此时此时正文代码。在调用函数值只需要用相对起始位置的偏移量即可。之后再通过页表去映射物理内存中。需要调用函数的实际地址。需要注意的是:可执行程序在加载时已经编译了虚拟地址。当函数中再次调用函数时,物理内存中记录的需要调用的函数的地址依旧是虚拟地址,这需要通过cpu的指令寄存器再一次用页表进行映射到物理内存
动态库中函数的调用细节流程
代码从正文的主函数开始运行,首先通过页表映射到物理内存的main函数并往下执行。此时需要调用虚拟地址为0x2222的函数。将该虚拟地址放入CPU的指令寄存器进行译码,再通过页表重新映射到物理内存,执行0X2222函数
[ 待补充… ]