[Linux]动静态库
文章目录
- [Linux]动静态库
- 见一见库
- 存在库的原因
- 编写库
- 模拟编写静态库
- 模拟使用静态库
- 模拟编写动态库
- 模拟使用静态库
- 库的加载原理
- 静态库的加载原理
- 动态库的加载原理
- 库在可执行程序中的编址策略
- 静态库在可执行程序中的编址策略
- 动态库在可执行程序中的编址策略
见一见库
在Linux系统中,C语言标准库(C Standard Library)和其他常用库都被称为C库(C library),通常存放在/usr/lib
或/usr/lib64
目录下:
在Linux系统中,/usr/include
目录下存放了许多头文件:
- Linux系统预装了C/C++的头文件和库文件,头文件提供方法的说明,库文件提供方法的实现,头文件和库文件有对应关系,需要组合使用。
- 在程序编译形成可执行程序的过程中,头文件在预处理时被引入,库文件在链接时被链接 。
- 在visual studio下安装开发环境时,不仅安装了编译器软件,还安装语言对应的头文件和库文件。
- 编译器会自动根据用户的输入,查找头文件中的相关内容,实现语法提示的功能。
- 编译器会自动根据用户的输入,不断地进行编译,实现语法报错的功能。
存在库的原因
编程语言将常用的功能添加到库中,以便于用户可以直接使用这些功能,提高开发的效率。比如printf
,用户不需要在每次想打印数据时,编写一个用于打印数据的函数。
编写库
模拟编写静态库
进行库的编写前要说明一下:
- 库分为动态库和静态库
- Linux下静态库的命名规则:
lib库名.a
- Linux下动态库的命名规则:
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 目标压缩包名 源文件
将库打包成压缩包:
模拟使用静态库
- 将前文操作中打包好的静态库压缩包复制到某一目录下并解压来模拟库的下载过程
- 在当前目录下创建源文件main.c并编写调用静态库的代码,具体代码实现如下:
#include <stdio.h>
#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;
}
- 将源文件main.c编译形成可执行程序:
由于第三方头文件不在编译的目录下,需要-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 目标压缩包名 源文件
将库打包成压缩包:
模拟使用静态库
- 将前文操作中打包好的动态库压缩包复制到某一目录下并解压来模拟库的下载过程
- 在当前目录下创建源文件main.c并编写调用静态库的代码,具体代码实现如下:
#include <stdio.h>
#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;
}
- 将源文件main.c编译形成可执行程序:
指定动态库头文件的路径、库文件的路径和库名后,编译器能够成功编译,由于是动态库,程序运行时需要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
就是获取这个被称为与地址无关码的地址偏移量,在实际进程运行时,进程只需要等待库中方法被加载到内存中并被映射到共享区,然后利用共享区映射加上偏移地址完成运行。
说明一下:
- gcc/g++编译器编译形成可执行程序时,默认使用动态库。
- gcc/g++编译器编译形成可执行程序时,使用
-static
选项,将使用静态库。 - gcc/g++编译器编译形成可执行程序时,部分动态库不存在,会采用动态库混合使用的方式。