在Linux系统中,C语言标准库(C Standard Library)和其他常用库都被称为C库(C library),通常存放在/usr/lib
或/usr/lib64
目录下:
在Linux系统中,/usr/include
目录下存放了许多头文件:
编程语言将常用的功能添加到库中,以便于用户可以直接使用这些功能,提高开发的效率。比如printf
,用户不需要在每次想打印数据时,编写一个用于打印数据的函数。
进行库的编写前要说明一下:
lib库名.a
lib库名.so
编写头文件myadd.h和其对应的源文件myadd.c和头文件mysub.h和其对应的源文件mysub.c,各文件中的具体代码如下:
//myadd.h
#pragma once
int my_add(int x, int y);
//myadd.c
#include "myadd.h"
int my_add(int x, int y)//一个简单的加法函数
{
return x + y;
}
//mysub.h
#pragma once
int my_sub(int x, int y);
//mysub.c
#include "mysub.h"
int my_sub(int x, int y)//一个简单的减法函数
{
return x - y;
}
使用gcc -c 源文件名
将源文件编译成目标文件:
使用ar -rc lib库名.a 目标文件
将目标文件打包成静态库:
创建include目录将头文件移动到该目录中,创建lib目录将静态库移动到该目录中:
使用tar -czf 目标压缩包名 源文件
将库打包成压缩包:
#include
#include "myadd.h"
#include "mysub.h"
int main()
{
int x = 20;
int y = 10;
printf("%d + %d = %d\n", x, y, my_add(x, y));
printf("%d - %d = %d\n", x, y, my_sub(x, y));
return 0;
}
由于第三方头文件不在编译的目录下,需要-I 路径
选项指明头文件路径,由于第三方库编译器不会自己查找和使用,需要-L 路径
指明库文件路径名,需要-l 库名
指明库名。
总结一下Linux系统下第三方库的使用:
-I 路径
)-L 路径
)-l 库名
)在模拟编写动态库时,沿用了前文中打包静态库使用的头文件myadd.h和其对应的源文件myadd.c和头文件mysub.h和其对应的源文件mysub.c。
打包动态库时需要使用gcc -fPIC -c 源文件名
将源文件编译成目标文件:
使用gcc -shared -o lib库名.so 目标文件
将目标文件打包成动态库:
创建include目录将头文件移动到该目录中,创建lib目录将静态库移动到该目录中:
使用tar -czf 目标压缩包名 源文件
将库打包成压缩包:
#include
#include "myadd.h"
#include "mysub.h"
int main()
{
int x = 20;
int y = 10;
printf("%d + %d = %d\n", x, y, my_add(x, y));
printf("%d - %d = %d\n", x, y, my_sub(x, y));
return 0;
}
指定动态库头文件的路径、库文件的路径和库名后,编译器能够成功编译,由于是动态库,程序运行时需要OS根据程序内的动态库地址链接到动态库才能成功运行,但是OS无法找到该动态库,就造成了下图的情况:
使用export LD_LIBRARY_PATH=LD_LIBRARAY_PATH:动态库所在目录路径
将动态库路径导入环境变量,OS在运行程序时会从环境变量中的路径找到动态库并成功运行:
解决第三方动态库OS查找不到的方法:
export LD_LIBRARY_PATH=LD_LIBRARAY_PATH:动态库所在目录路径
将动态库路径导入环境变量,环境变量会在重新打开shell时重新加载,因此是临时方案sudo ln -s 动态库路径 /lib64/lib库名.so
将动态库的软链接添加到系统路径下/etc/ld.so.conf.d/
路径下创建后缀为.conf
文件,将静态库的路径写入该文件,然后使用sudo ldconfig
使配置文件生效。动态库的加载过程就是在形成可执行程序的链接过程中直接将静态库中的实现拷贝至可执行程序中。因此静态库十分占用资源(磁盘、内存、网络资源)。
首先,使用动态库生成可执行程序时,在链接过程中,可执行程序中只会将代表库中方法的外部符号替换成对应地址,由于形成可执行程序中没有具体的实现,因此要想运行起来,操作系统做了一系列的工作,在程序被加载到内存中形成进程后,操作系统会为其维护进程控制块和进程地址空间和页表等:
在进程运行到动态库中的方法后,操作系统会在页表中寻找映射,发现映射到内存中的只是一个对应地址而不是具体方法实现,因此操作系统寻找这个动态库,按照一定策略将动态库加载到内存中,然后操作系统会将加载到内存中的动态库映射给进程地址空间中在栈区和堆区之间的共享区:
而后,每次该进程执行该库中方法时,只需要跳转到进程地址空间中的共享区,就可以完成程序的执行:
另外,当该库被加载到内存中后,后续运行的进程需要执行该库方法时,不需要再在内存中加载库,而是直接创建共享区映射,然后使用库中方法。
形成可执行程序时,可执行程序中会存在逻辑地址,如果采用的是静态库,可执行程序中静态库的方法也会被编址,获得一个逻辑地址,在程序变成进程运行时,只需要根据逻辑地址进行跳转即可。
形成可执行程序时,可执行程序中会存在逻辑地址,如果采用的是动态库,可执行程序中动态库的方法也会被编址,但该地址是库中方法在库中从起始地址开始的偏移量,在制作动态库的获取目标文件的操作时,使用gcc添加-fPIC
就是获取这个被称为与地址无关码的地址偏移量,在实际进程运行时,进程只需要等待库中方法被加载到内存中并被映射到共享区,然后利用共享区映射加上偏移地址完成运行。
说明一下:
-static
选项,将使用静态库。