Linux 高级编程
库(Library)
库分为:
- 源码库
- 静态库:也称为静态链接库(Static Link Library)
- 动态库:也称为共享库(Shared Library)、动态链接库(Dynamic Link Library)
通过源码库可以实现源代码级别的复用,通过静态库或共享库可以实现二进制级别的复用(保护自己的源代码)
一般情况下被调用者拥有的是:头文件、库文件、帮助手册。
制作静态库文件的步骤:
- 将相关源文件(.c)编译为对应的目标文件(.o),使用
gcc -c 源文件名称(xxx.c)
命令 - 将上一步得到的所有目标文件打包为静态库,使用
ar -cr 静态库的名(libxxx.a) 目标文件(a.o)
命令
使用静态库的命令
- 多个库
gcc 调用者源文件 -l静态库名(不写lib和后缀.a) -L路径(.为当前路径) -o 可执行文件名
- 单个库,无需打包,同一路径下
gcc 调用者源文件 被调用者目标文件(.o) -o 可执行文件
静态库其实就是若干个目标文件的归档文件。
制作动态库的命令
gcc -shared -fPIC 源文件名(***.c) -o 动态库文件名(lib***.so)
使用动态库的命令
- 当前路径下:
gcc main.c -L. -l动态库名(不带.so) -o 可执行文件名
- 配置完后(配置方法在后续)
gcc main.c -l动态库名(不带.so) -o 可执行文件名
- 直接使用或指定(当前)路径
gcc main.c 动态库名.so -o 可执行文件名
gcc main.c ./动态库名.so -o 可执行文件名
在 Linux 系统上,静态库文件的后缀名为 .a,动态库文件的后缀名为 .so。
在Windows系统上,静态库文件的后缀名为 .lib,动态库文件的后缀名为 .dll
在编译时通常是通过 -L 和 -l 参数指定需要链接的具体库文件,前者指定库文件的搜索路径,后者指定库文件名称(不算前缀 lib) 。如果同时存在同名的静态库和动态库,则优先链接动态库。
在启动程序时,加载器(将动态库文件加载到内存中,加一次就可)会解析可执行程序,获得该程序依赖的所有动态库信息,然后在磁盘上找到它们,并将它们加载到内存中(如果之前没有加载过),如果加载器无法找到目标动态库文件或加载失败,那么程序就启动失败了,无法执行。
通过 ldd
命令可以查看可执行文件依赖的动态库文件以及路径。
报错:
./main:error while loading shared libraries:lib动态库名.so:connot open shared file:No such file or directory
让链接器和加载器找到某个动态库文件的三种方法:
-
将动态库文件拷贝到 /lib、/user/lib这两个文件夹下;
-
设置 LD_LIBRARY_PATH 这个环境变量的值为库文件路径;(只对当前终端有效)
export LD_LIBRARY_PATH=.
-
修改 /etc/ld.so.conf 这个配置文件,将库文件的绝对路径写入其中,然后执行ldconfig 命令(刷新),让配置生效。
静态链接库和动态链接库的区别:
- 静态库在链接时的相关代码会被链接器提取出来,嵌入到在可执行文件中(即静态链接),成为可执行文件的一部分,所以生成的程序可以不依赖静态库独立运行,但生成的程序的体积较大。如果系统中有多个程序需要调用库文件中的函数,那么每个程序都包含一份静态库的代码,白白浪费了大量的存储空间。在程序需要更新升级时,只能重新编译生成新的可执行程序替换掉旧版程序,即全量更新,这对于大型软件来说是非常致命的。
- 动态库在链接时,链接器只是将其描述信息(动态库名、内部的函数名或全局变量名称等)提取出来嵌入到可执行文件中,所以生成的程序体积很小,但在运行时必须加载动态库到内存中(即动态链接),否则程序会启动失败,也就是说程序无法独立运行。如果系统中有多个程序都需要调用库文件中的函数,动态库只会被加载一次,也就是说内存中永远只有一份动态库的代码,所有程序共享一份。另外,在程序需要更新升级时更加方便,可以实现局部更新 (对外函数接口保存不变,用新版动态库直接替换旧版即可,无需重新编译可执行程序。)
动态库的两种使用方式:
- 隐式调用:系统在程序启动时自动加载依赖的所有动态库,一旦某一个动态库加载失败,程序就无法启动,并且我们对此无法处理。这种方式使用起来更简单,甚至感觉不到动态库的存在,所以用的更多。
- 显示调用:自己通过代码控制动态库的加载和卸载,根据需求调用其中的函数。这种方式使用起来比较繁琐,但灵活性很高,在必要时可以使用。
显示调用例子:
#include <dlfcn.h> //包含的头文件
void (*SortFun)(void *base,size_t nmemb,size_t size, int (*comper)(const void*,const void*));
int main(){
void* lib = dlopen("./libdjsort.so.1.0.1",RTLD_NOW);//打开库
if(NULL == lib){
//自定义处理
printf("error\n");
return 0;
}
SortFun f dlsym(lib, "quick_sort");找库中的函数,需要指定函数名,有返回函数的首地址,没有返回NULL;
if(f != NULL)
{
int nums[] = {5, 3, 4, 1, 2};
f(nums, 5, sizeof(int), cmp);
for(int i=0;i < 5; i++)
{
printf("%d",num[i]);
}
}
dlclose(lib);//关闭
}
gcc main.c -ldl //编译时需要带上dl库
关于dlopen函数的使用方法:https://blog.csdn.net/woyebuzhidao888/article/details/46634591
版本号通常的形式为:x.y.z,x 称为主版本号,y 称为次版本号, z 称为修订号或补丁号。
通常版本需要更新,可执行文件的库名称没有改,比如liba.so.1.0.0(老),liba.so.1.0.1(新)
使用软链接的方式:创建软链接名为:liba.so.1.0.1指向liba.so.1.0.0
ln -s liba.so.1.0.1 liba.so.1.0.0
帮助手册的编写(以qsort函数为例)
### 帮助手册
<函数名>qsort
## 函数原型
<头文件>#include "qsort.h"
<函数声明>void qsort(void *base, size_t nmemb,
size_t size, int (*compar)(const void *, const void *));
## 参数描述
base:***
nmemb:***
...
## 返回值
函数成功返回0,失败返回1
## 示例
***