前言:
- 本期我将要给大家讲解的是有关 动态库和静态库 的相关知识!!!
序言
见一见库
本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和 动态库(.so、.dll)。
- 所谓静态、动态是指链接。简单回顾一下将一个程序编译成可执行程序的步骤:
在正式的讲解动态库和静态库之前,我们先带带大家认识我们平时写 C/C++代码时用到的库
- " ls /usr/lib64/libc* ” 这个命令用于列出匹配模式 "libc*" 的文件或目录
- 其次,我们也可以去查看平时我们经常用到的:
【解释说明】
- 系统已经预装了C/C++的头文件和库文件,头文件提供方法说明,库提供方法的实现,头和库是有对应关系的,是要组合到一起使用的;
- 头文件是在预处理阶段装入的,链接本质其实就是链接库!!!
为什么要有库
其实这一点大家都能理解(造轮子和用轮子的问题):
- 库是一组预先编写好的代码、程序或函数的集合,用于解决特定类型问题或提供特定功能;
- 这些代码通常被封装成模块,以便在开发软件时能够重用、共享和简化代码。
在我们学习阶段大家可以更多的尝试去造轮子,熟悉其中的一些思想及其方法等,等到大家上班的就是 “用轮子” 的较多了。
(一)动态库(.so)
1.基本概念
- 动态库是一种在运行时加载和链接的库文件;
- 与静态库相比,动态库在程序执行之前不会被完全链接到可执行文件中,而是在运行时被动态地加载到内存并链接。
2.命名规则
在命名动态库时,通常会遵循一定的命名规则以确保一致性和可读性。以下是一些常见的动态库命名规则:
- 前缀:动态库名称通常以lib开头作为前缀,表示这是一个库文件。
- 中间部分:中间部分通常是描述库的名称或功能的字母数字组合。可以根据库的用途或模块来命名,以便更好地理解其功能。建议使用小写字母和下划线进行命名,以增加可读性。
- 扩展名:动态库的扩展名取决于操作系统和平台。在Linux上,通常是.so(共享对象);在Windows上,通常是.dll(动态链库);在Mac上,通常是.dylib(动态库)。
💨 综合起来,一个典型的动态库命名可能如下所示:
3.制作动态库
在Linux系统上创建动态库(也称为共享库或.so文件)涉及编写、编译和链接一组源代码文件,以生成可在多个应用程序之间共享的动态链接库。
以下是创建Linux动态库的一般步骤:
1️⃣编译源代码:
使用编译器将源代码编译成位置无关的目标文件,通常使用.o
文件扩展名。为了生成动态库,需要使用 -fPIC
选项(位置无关代码)来确保目标文件可以在内存中加载并重定位。
- 例如,在Linux系统上,可以使用
gcc
编译器编译一个源文件并生成位置无关的目标文件:
2️⃣创建动态库:
使用共享库工具(gcc
也可以用于此)将目标文件链接到一个共享库文件中。通常,Linux动态库的命名约定是以.so
结尾,并且通常有版本号。
- 以下是创建动态库的示例,命名为 libmymath.so
3️⃣运行代码:
我这里是把文件打包拷贝到 【otherPerson】目录下执行:
💨 紧接着执行:gcc -o mytest main.c (发现报错了!!)
【解释说明】
- 因为在当前路径下头文件并没有找到,因为并没有进行安装
💨 接下来,再次执行:gcc -o mytest main.c -I include (发现还是报错了!!)
【解释说明】
- 因为在当前路径下库并没有找到
💨 接下来,再次执行:gcc -o mytest main.c -I include -L lib(发现还是报错了!!)
💨 接下来,再次执行:gcc -o mytest main.c -I include -L lib -lmymath(发现还是报错了!!)
这次,我们发现程序正常运行成功了。紧接着,我们执行相应的文件:
【解释说明】
- 此时,有的小伙伴可能就比较疑惑:“我不是已经告诉了系统,我的库在哪里并且告诉叫什么了吗?为什么还是找不到呢?”
- 其实,gcc时只是告诉了编译器,而没有真正的告诉操作系统!运行的时候因为我的 .so 并不是在默认的路径下,所以os 依旧找不到!!
⭐️ 如何让系统能够找到动态库:
- 1、通过环境变量(临时方案)
环境变量中确实没有我们当前的路径,因此第一种解决方法就是把我们当前的路径加入到系统的环境变量中。具体如下:
但是,这个方法是临时性的,当我们重新打开去运行时就失效了:
- 2、指定路径下建立软链接(永久版本)
- 3、配置文件方案
在配置之前,需要先把上述使用软链接创建的删除掉:
💨如果安装在其他目录,需要将其添加到/etc/ld.so.conf.d/文件中,步骤如下:
1.前期准备
2.编辑/etc/ld.so.conf文件,加入库文件所在目录的路径
3.运行ldconfig ,该命令会重建/etc/ld.so.conf.d/文件
(二)静态库(.a)
1.基本概念
- 静态库是一种在编译时链接到可执行程序的库文件形式;
- 它是由一组预编译的目标文件(Object File)组成,其中包含了函数、变量和其他可重用的代码和数据。
💨 需要注意一点:库云服务器默认没有静态!!!
2.命名规则
在 Linux 上,静态库的命名规则通常是:
lib<library_name>.a
其中:
- lib 是固定的前缀,表示这是一个库文件。
- library_name 表示该库的名称或功能,建议使用小写字母和下划线进行命名,以增加可读性。
- .a 是静态库的扩展名,表示这是一个归档文件 (archive file)。静态库通常以 .a 作为扩展名。
💨 综合起来,一个典型的动态库命名可能如下所示:
3.制作静态库
创建静态库涉及编写、编译和打包一组代码文件,以便它们可以在其他程序中静态链接和重用。
以下是创建静态库的一般步骤:
- 1、编写使用创建的静态库的测试代码:
/myadd.h/ #ifndef __ADD_H__ #define __ADD_H__ int add(int a, int b); #endif // __ADD_H__ /myadd.c/ #include "myadd.h" int add(int a, int b) { return a + b; } /mysub.h/ #ifndef __SUB_H__ #define __SUB_H__ int mysub(int a, int b); #endif // __SUB_H__ /mysub.c/ #include "mysub.h" int sub(int a, int b) { return a - b; } ///main.c #include <stdio.h> #include "myadd.h" #include "mysub.h" int main() { int a = 10; int b = 3; printf("add(10, 3)=%d\n", a, b, add(a, b)); printf("add(10, 3)=%d\n", a, b, sub(a, b)); return 0; }
- 2、使用编译器将源代码编译成目标文件(通常是中间代码文件),编译时,使用适当的编译选项以确保生成的目标文件是可链接的(注意带参数-c,否则直接编译为可执行文件)
- 3、然后,通过ar工具将目标文件打包成.a静态库文件
【注意】
- 大一点的项目会编写makefile文件(CMake等等工程管理工具)来生成静态库,输入多个命令太麻烦了
- 4、Linux下使用静态库,只需要在编译的时候,指定静态库的搜索路径(-L选项)、指定静态库名(不需要lib前缀和.a后缀,-l选项)
【注意】
- -L:表示要连接的库所在目录
- -l:指定链接时需要的动态库,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a或.so来确定库的名称。
【小结】
- 创建静态库涉及编写、编译、打包目标文件,然后将静态库链接到其他程序中;
- 这种方法适用于小型项目,或者当需要确保可执行文件与特定版本的库一起分发时;
- 但需要注意的是,静态库会增加可执行文件的大小,并且不支持动态更新。如果需要更大的灵活性和共享性,可以考虑使用动态库。
(三)什么叫 fPIC
fPIC 是 GCC编译器的一个选项,用于生成位置无关代码(Position Independent Code,PIC)。位置无关代码是一种可以在内存中加载并且不受加载地址限制的代码,因此适合用于创建共享库(动态链接库,也称为.so文件)以及在多个进程之间共享的代码。
- 我们以下图进行分析:
【解释说明】
- 当一个库真正的映射到地址空间的时候,它的起始地址才能真正的被确定下来!而它的库在形成的时候并不是使用的绝对地址,而是使用的相对于起始地址的偏移量;
- 有了上述这样的方法,使得动态库在进程的地址空间中能够随便的进行加载,而与我们加载到地址空间的什么位置毫无关系。因为OS是知道起始地址的,因此在我们链接时只需把对应的偏移量加载进来,OS根据起始地址 + 偏移量的操作即可完成相应的地址映射;
- 上述这样的库就称为动态库,这种库中的地址就称为 fPIC。
(四)对比静态库和动态库
动态库和静态库是两种不同的库文件类型,它们在编程和软件开发中有着重要的区别。以下是它们之间的主要区别:
- 加载时机:
- 静态库:在编译时将库的代码和数据链接到可执行文件中,形成一个单独的可执行文件。这意味着库的代码在程序运行之前已经完全被包含在可执行文件中。
- 动态库:库的代码和数据在程序运行时才被加载到内存中。可执行文件包含了对库的引用,但实际的库代码在程序启动时或首次使用时才会被加载。
- 文件大小:
- 静态库:由于库的代码被完全复制到每个可执行文件中,因此可执行文件通常比较大。
- 动态库:多个可执行文件可以共享相同的库,因此动态库可以减小可执行文件的大小。
- 内存占用:
- 静态库:每个运行的程序实例都包含库的一个拷贝,因此可能会占用更多的内存。
- 动态库:多个程序实例可以共享同一个库的实例,节省内存。
- 可扩展性:
- 静态库:每次引入新的库版本都需要重新编译和链接,不够灵活。
- 动态库:可以在不修改可执行文件的情况下,替换或升级库的版本,提供更好的可扩展性。
- 加载速度:
- 静态库:由于库的代码已经包含在可执行文件中,因此加载速度较快。
- 动态库:需要在运行时加载,可能会略微减慢程序启动速度。
总结
以上便是本文关于动静态的全部内容了。接下来,简单的回顾总结一下!!
- 动态库和静态库各自有其优势和劣势,具体选择取决于项目的需求和设计考虑;
- 通常,动态库在节省内存、库的更新和维护方面更有优势,而静态库在加载速度和跨平台兼容性方面更有优势。