序言
这是文件体系的最后一节内容,在本篇文章中我们将先容动静态库。信赖在各人编程都多多少少会用到库,利用库确实极大地便利了我们的编程工作,不但提高了开发服从,还使得代码更加模块化和可重用。
通过这篇文章的学习,我们将相识到什么是动静态库?是怎么实现的?背后的原理是什么?
1. 静态库
1.1 静态库的概念
静态库是指将一些公共代码编译成 库 文件,在链接步骤中,连接器(Linker)会从这些库文件中取得所需的代码,并将它们 直接复制到生成的可执行文件中。这种方式被称为静态链接(Static Linking)。在内存中的图像如下:
1.2 静态库的特点
- 代码复制:利用静态库时,库中的代码会被直接复制到终极的程序中。这意味着终极的可执行文件会包罗所有必要的库代码,因此在运行时不必要再额外加载库文件。
- 单一拷贝:由于库代码被复制到可执行文件中,所以假如有多个程序利用了同一个静态库,那么在每个程序中都会存在该库代码的一份独立拷贝。这可能会 导致终极的可执行文件体积较大。
- 更新困难:假如静态库中的代码必要更新,那么所有利用了该库的程序都必要 重新编译和链接,以便包罗新的库代码。这可能会增加维护本钱。
静态库的操纵相比于动态库是较简单的,直接将利用的库的代码拷贝到我们的程序中,但是这会导致我们的程序的体积较大,并且必要修改静态库时,利用了静态库的程序全都要重新写入一遍,消耗非常大!
1.3 静态库的实现
1. 前置工作
在这里我们尝试实现一下静态库,并且静态链接到我们的程序。首先我简单的实现了一个 Mymath.cc 文件:
- 1 #include "Mymath.h"
- 2
- 3 int Add(const int &left, const int &right){
- 4 return left + right;
- 5 }
- 6
- 7 int Sub(const int &left, const int &right){
- 8 return left - right;
- 9 }
复制代码 还包罗一个 Mymath.h,来声明我实现了哪些函数以及头文件。
之后我实现了一个 Main.cc 函数,该函数主要是调用我们实现的函数:
- Main.cc X
- 1 #include "Mymath.h"
- 2
- 3 int main(){
- 4 int A = 1;
- 5 int B = 1;
- 6
- 7 std::cout << "A + B = " << Add(A, B) << std::endl;
- 8
- 9 return 0;
- 10 }
复制代码 2. 编译打包
首先我们必要将我们实现的函数编译为 .o 的目标文件:g++ -c Mymath.cc,之后我们将我们的所有 .o 文件打包。(在本篇文章中,只有一个 .o 文件,但是在实际的场景下,会包罗许多个该文件)
打包的指令是 ar rc libMymathc.a *.o (在这里 rc 选项代表 replace and create 存在就覆盖,不存在就创建)。实在在这里我们就不难知道,所谓的库文件也就是许多的 .o 文件打包。
注意:打包的静态文件一般都以 .a 结尾,以 lib 开头。
3. 静态库链接
现在我们有了 .h 文件并且将我们的所有 .o 文件打包成了一个 .a 文件,那该怎么利用呢?有两个方法:
安装到库
我们利用的所有 C/C++ 的官方库函数头文件都存放在当地的堆栈里,就好比所有头文件存放在 /usr/include ,而所有的打包好的库函数存放在 /usr/lib64,当利用某个函数时会自动前往库函数中寻找。我们现在制造的库函数,想要利用也可以放入库函数中:
放入头文件:sudo cp ./mylib/include/Mymath.h /usr/include/
放入包:sudo cp ./mylib/lib/libMylib.a /lib64
将自己的库函数放入到体系指定目次,实在这就是安装。
现在库也安装好了,是不是我们就直接可以 g++ Main.cc 编译程序形成可执行文件了呢?还不够,由于 g++ 默认是只认识 C/C++ 的官方库函数,不认识我们这种第三方库,所以我们还必要告诉他我们利用了哪个第三方库:g++ Main.cc -l mylib 。(注意:我们库的名字必要去掉前缀 lib 和后缀 .a),就可以啦!
指定搜刮路径
我们的程序编译时会自动寻找利用的库函数,但是查找的路径只限于官方库函数的路径,所以我们可以指定搜刮路径,我们必要利用到 g++ 的新选项:
然后我们的指令为:
编译时指定搜刮路径:g++ Main.cc -I ./mylib/include -L ./mylib/lib/ -l Mylib -static
是的,这个指令非常的长,我来为各人分段解释一下:
- -I:该选项后跟你头文件所在的目次,不必要指定详细利用了哪些头文件
- -L:该选项后跟你包所在的路径,并且必要指定所用的那些包,利用 -l 后跟着利用的包(命名和上一致)
- -static:指名必要静态连接
所以说,大概就这两种方式来利用静态库。
2. 动态库
2.1 动态库的概念
动态库是一种 在程序运行时被加载和链接的库文件。与静态库差别,动态库在编译时不会被直接复制到可执行文件中,而是在程序运行时 根据必要动态地加载到内存中。在内存中的图像如下:
2.2 动态库的特点
- 运行时独立存在:动态库在程序运行时被加载到内存中,而不是在编译时静态地链接到执行程序中。这意味着动态库可以作为独立的文件存在,并 在多个程序之间共享。
- 代码复用与节流空间:动态库答应 多个程序共享同一份代码,这种代码复用机制 不但镌汰了磁盘空间的利用,还镌汰了内存中的重复代码量,提高了体系的整体服从。
- 更新方便:由于动态库是 独立于程序存在的 ,因此当动态库必要更新时,只需替换旧的动态库文件即可,而 无需重新编译或链接依靠于该动态库的程序。这使得软件的更新和维护变得更加方便和快捷。
2.3 动态库的实现
1. 前置工作
我们在这里和静态库复原一份代码,将该份代码分别制作动静态库。
2. 编译打包
首先,我们必要将该函数编译为 .o 的目标文件,和静态链接的差别在这里我们必要加上 -fpic 选项,所以指令是:g++ Mymath.cc -fPIC -c。
之后,我们必要将所有相关的 .o 目标文件举行打包,打包的指令是:
gcc -shared -o libMylib.so Mymath.o。由于我们绝大部分制作的库都是动态库,所以 g++ 直接提供构造方式。
注意:打包的静态文件一般都以 .so 结尾,以 lib 开头。
3. 动态库链接
现在也打包好了,那该怎么利用我们制作的动态库呢?和静态库一样,要生成可执行文件,我们要么安装到官方库,要么指定搜刮路径,我们选择后者:
g++ Main.cc -I ./mylib/include -L ./mylib/lib -lMylib,不错,没有任何报错,也生成了可执行文件,现在运行 ./a.out:
ubuntu@VM-24-13-ubuntu:~/8_8$ ./a.out
./a.out: error while loading shared libraries: libMylib.so: cannot open shared object file: No such file or directory
咦,怎么出错啦,找不到该文件?
首先,我们明确静态库,当链接通过期,我们 静态库中的函数直接拷贝到了我们的程序中,但是动态库在链接时,只是让我们的程序和动态库创建某种关联,所以在运行时必要找到动态库,加载运行!!!
现在我们必要程序在运行时链接到动态库文件,该怎么办呢?当利用动态库时,编译器会自动到 /usr/lib64 文件下寻找该文件,所以我们可以采取:
将动态库文件安装到库中
这个方法在上面先容过,在此不多先容。
创建动态库文件的快捷方式到库中
不直接将我们的动态库文件放入体系的库中,将我们的动态库文件的软链接放入,体系依然可以找到:
sudo ln -s /home/ubuntu/8_8/mylib/lib/libMylib.so /lib64/libMylib.so
更改搜刮路径环境变量
体系在查找动态库时,是根据环境变量找到动态库的位置,所以我们可以将我们动态库所处的位置加载到环境变量中,
体系查找路径对应的环境变量是:
LD_LIBRARY_PATH
添加方式为:
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:YOUR_PATH
但该添加是内存级别的,重启后会消失,若想要永久的,可更改设置文件,在此不多赘述。
完成以上三种任意一种后,就可以正常执行我们的动态库啦!
3. 动静态库的原理
3.1 静态库的原理
静态库的原理比力简单,主要就是链接器会从静态库中提取出被目标文件引用的函数和数据,并将它们复制到可执行文件中。因此,当程序运行时,它不再必要静态库文件,由于所有必要的代码和数据都已经被包罗在了可执行文件中。
3.2 动态库的原理
静态库在编译时链接,而动态库是在 运行时链接 ,这也反映了动态库的原理肯定是更难的。下面我将逐步中依次讲解如何链接,会涉及到底层,但是过底层的知识不会涉及。
1. 查找动态库
通过 历程地点空间 的学习,我们相识到 操纵体系执行程序时通常是按照虚拟地点执行的,这些虚拟地点在执行过程中会通过页表转换到物理内存上,所以当我们尝试调用动态库中的函数时,发现该函数并不存在于物理内存上,所以动态链接器 会查找相应的动态库文件,并将其加载到内存中。
内存上包罗一块区域是专门管理存储动态库的,当程序必要某个动态库时,会在这块区域查找,假如未查找到,会将该动态库加载。
2. 将动态库加载到历程地点空间并创建映射
各人是否还记得我们的历程地点空间图像:
在堆栈之间存在一个区域叫做 — 共享区,所以 动态库中的函数和数据映射到历程的地点空间的共享区中。现在动态库存在于历程地点空间中了,他有了虚拟地点;同时他也存在于物理内存中,他也有了物理内存地点。
最后就是将动态库的虚拟地点和物理内存地点通过页表创建映射关系。
3. 调用的动态库中的函数
现在预备工作都做好了,就等着别人来调用了。一个动态库里面可能实现了许多的函数,那怎么锁定程序必要函数的位置呢?
首先,我现在手里有了库函数在共享区的虚拟地点,实在我还有该函数在库中位置的偏移量(调用的函数名),接纳 虚拟地点 + 偏移量 的方法就可以定位到我们必要函数的位置。
所以动态库可以映射到共享区的任何位置,由于尽管虚拟地点发生了变革,但是偏移量是肯定的,接纳 虚拟地点 + 偏移量 的方法总能定位到函数的位置。
4. 总结
在这篇文章中先容了动静态库的实现,原理,以及制作,渴望各人有所收获。 |