什么是库文件
单一模型:
将程序中的所有功能全部实现于一个单一的源文件内部,编译时间长,不易于维护和升级,不易于协助开发。
分离模型:
将程序中不同功能的模块划分到不同的源文件中。缩短编译时间,易于维护和升级,易于合作开发。
可以简单的把库文件看作代码仓库,它提供使用者一些可以直接拿走使用的变量、函数、类等。库文件一般指计算机上的文件,一种是动态文件,一种是静态文件。
静态库
* 静态库的本质是将多个目标文件打包成一个文件
* 链接静态库就是将库中调用的代码复制到调用的模块中
* 静态库的拓展名 .a 例:libxxx.a (以lib开头)
静态库的构建
* 编译库的实现代码和接口说明
模块1:module_1.h,module_1.c
模块2:module_2.h,module_2.c
接口文件:xxx.h
* 编译成目标文件
gcc -c module_1.c
gcc -c module_2.c
* 打包成静态库文件
ar -r libxxx.a module_1.o module_2.o
ar命令
ar [选项]<静态库文件><目标文件列表> (<目标文件列表>是.o文件)-r 将目标插入到静态库中,已存在则更新
-q 将目标文件追加到静态库尾
-d 从静态库中删除目标文件
-t 列表显示静态库中的目标文件-x 将静态库展开为目标文件
静态库的使用
* 编辑库的使用代码
例如:main.c
* 编译并链接静态库
直接链接静态库gcc main.c libxxx.a
用-l指定库名,用-L指定库路径
gcc mian.c -lxxx -L. //libxxx.a去掉lib和后缀.a
用-|指定库名,用LBRARY_PATH环境变量指定库路径
export LIBRARY_PATH=$LIBRARY_PATH:.
gcc main.c -lxxx //libxxx.a去掉lib和后缀.a
动态库
* 动态库和静态库不同,链接动态库不需要将被调用的函数代码复制到包含调用代码的可执行文件中,相反链接器会在调用语句处嵌入一段指令,在该程序执行到这段指令时,会加载该动态库并寻找被调用函数的入口地址并执行之
* 如果动态库中的代码同时为多个进程所用,动态库在内存的实例仅需一份,为所有使用该库的进程所共享,因此动态库亦称共享库。
* 动态库的拓展名是.so 例libxxx.so
动态库的创建
* 编辑库的实现代码和接口声明
模块1:module_1.h,module_1.c
模块2:module_2.h,module_2.c
接口文件:xxx.h
* 编译成目标文件
gcc -c -fpic module_1.c
gcc -c -fpic module_2.c
* 打包成动态库
gcc -shared module_1.o module_2.o -o libxxx.so
* 编译链接也可以合并成一步完成
gcc -shared -fpic module_1.c module_2.c -o libxxx.so
PIC(Position Independent Code,位置无关代码)调用代码通过相对地址标识调用代码的位置,模块中的指令与该模块被加载到内存中的位置无关
fPIC:大模式,生成代码比较大,运行速度比较慢,所有平台都支持
fpic:小模式,生成代码比较小,运行速度比较快,仅部分平台支持
动态库的使用
* 编辑库的使用代码
main.c
* 编译并链接动态库
直接链接动态库
gcc main.c libxxx.so用-l指定库名,用-L指定库路径
gcc mian.c -lxxx -L.
用-|指定库名,用LIBRARY PATH环境变量指定库路径
export LIBRARY_PATH=$LIBRARY_PATH:.
gcc main.c -lxxx
注意
* 运行时可执行文件时需要保证LD_LIBRARY_PATH环境变量中包含共享库所在的路径用以告知链接器在运行时链接动态库动态库
export LIBRARY PATH=$LIBRARY PATH:.
* 在可执行程序的链接阶段,并不将所调用函数的二进制代码复制到可执行程序中,而只是将该函数在共享库中的地址嵌入到调用模块中,因此运行时需要依赖共享库
优缺点
静态库
* 静态库的优缺点:
优点:
执行速度快可执行程序不依赖库的存在
缺点:
文件体积相对较大更新困难,维护成本高
动态库
* 动态库的优缺点:
优点:
可执行文件体积小,节省空间易于链接,便于更新维护
缺点:
文件执行速度相对较慢可执行程序依赖库文件的存在
动态库的动态加载
* 在程序执行的过程中,开发人员可以动态加载共享库(什么时候用什么时候加载 )
* 在程序中动态加载共享库需要调用一组特殊的函数,它们被声明于一个专门的头文件中,并在一个独立的库中予以实现。
* 使用这组函数需要包含此头文件,并链接该库
#include<dlfcn.h>
-ldl
void* dlopen(char const* filename, int flag);
*功能:将共享库载入内存并获得其访问句柄
* 参数:filename 动态库路径,若只给文件名不带目录,则根据 LD_LIBRARY_PATH环境变量的值搜索动态库
* flag 加载方式,可取以下值
RTLD_LAZY-延迟加载,使用动态库中的符号时才真的加载进内存。
RTLD_NOW -立即加载。
返回值:成功返回动态库的访问句柄,失败返回NULL。句柄:句柄唯一地标识了系统内核所维护的共享库对象,将作为或许函数调用的参数
void* dlsym(void* handle, char const* symbol);
* 功能:从已被加载的动态库中获取特定名称的符号地址
* 参数:handle 动态库访问句柄
symbol 符号名
* 返回值:成功返回给定符号的地址,失败返回NULL。该函数所返回的指针为void*类型,需要造型为与实际目标类型相一致的指针才能使用。
int dlclose(void* handle);
* 功能:从内存中卸载动态库
* 参数:handle 动态库句柄
* 返回值:成功返回0,失败返回非0。
* 所卸载的共享库未必会真的从内存中立即消失,因为其他程序可能还需要使用该库,只有所有使用该库的程序都显示或隐式地卸载了该库,该库所占用的内存空间才会真正得
到释放
* 无论所卸载的共享库是否真正被释放,传递给dlclose函数的句柄都会在该函数成功返回后立即失效
char* dlerror(void);,
* 功能:获取在加载、使用和卸载共享库过程中所发生的错误* 返回值:有错误则返回指向错误信息字符串的指针,否则返回NULL。
例子:
1 #include <stdio.h>
2 #include <dlfcn.h>
3
4 int main()
5 {
6 void *fd = dlopen("libmath.so", RTLD_LAZY);
7 int (*add)(int, int) = (int (*)(int,int))dlsym(fd, "add");
8 if(!add) {
9 printf("add get address failed\n");
10 return 0;
11 }
12 printf("add(20, 30) = %d\n", add(20, 30));
13 return 0;
14 }
编译:gcc shared.c -ldl
两个命令
* 查看符号表:nm
列出目标文件、可执行程序、静态库、或共享库中的符号例:nm libxxxx.a
* 查看依赖:ldd
查看可执行文件或者共享库依赖的共享库例:ldd a.out