【Linux】动静态库

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
🌎推荐文章:【LeetCode】winter vacation training

在这里插入图片描述


👉🏻动静态库概念

🌈静态库概念
当编译和链接程序时,代码和函数通常被组织成不同的库,其中一种常见的库类型是静态库(Static Library)。静态库是一组预编译的对象文件的集合,它们被打包在一个单独的文件中,以供程序在编译时静态链接使用。

静态库包含了编译过的目标代码,可以被多个程序共享使用。它的主要特点是在编译时将库的代码和数据复制到可执行文件中,使得可执行文件独立于系统上是否存在该库的副本。这意味着在使用静态库时,程序不需要依赖外部的共享库文件,因为所有必要的代码都已经被提前包含在了可执行文件中

使用静态库的优点包括:

  • 简单:使用静态库不需要额外的运行时依赖,因为所有代码都被复制到了可执行文件中。
  • 独立性:生成的可执行文件可以在不同的系统上运行,而无需安装和配置额外的库文件
  • 性能:静态库的代码被编译和链接到可执行文件中,可以获得更好的执行效率。

然而,静态库也存在一些限制和缺点:

  • 文件大小:由于静态库的代码被复制到每个可执行文件中,可能导致可执行文件的大小增加
  • 更新和维护:如果静态库需要更新,所有使用该库的程序都需要重新编译和链接以使用新的版本。

总结来说,静态库是一种包含预编译目标代码的库文件,它在编译时被静态链接到可执行文件中。它提供了独立性和性能优势,但可能导致可执行文件增大并需要额外的维护工作。

🌈动态库概念
动态库(Dynamic Linking Library)是一种包含可重用代码和数据的库文件,它在程序运行时被动态加载到内存中,并与程序共享使用。与静态库不同,动态库的代码和数据不会在编译时被复制到可执行文件中,而是在运行时通过动态链接器进行加载和链接。

动态库具有以下几个关键特点:

  1. 动态加载:动态库在程序运行时才被加载到内存中,而不是在编译时被静态链接到可执行文件中。这意味着程序可以在运行时根据需要加载或卸载动态库,从而实现更灵活的模块化设计。

  2. 共享性:多个程序可以同时使用同一个动态库,这样可以节省系统资源并提高代码重用性。动态库的共享性使得不同的程序可以共享相同的函数、类、变量等,减少了重复编写和维护的工作量。

  3. 运行时依赖:使用动态库的程序依赖于系统中已安装的动态库文件。如果某个程序需要使用某个动态库,但系统中没有该库,那么程序将无法正常运行。因此,在部署程序之前,需要确保所依赖的动态库已经正确安装在目标系统上。

  4. 灵活性:由于动态库的代码和数据可以在运行时加载,因此可以实现动态更新和升级。如果需要更新动态库的版本,只需要替换库文件即可,而不需要重新编译整个程序

  5. 性能优化:动态库的代码和数据可以被多个程序共享,这可以减少内存占用并提高执行效率。此外,动态库的加载是按需进行的,可以减少程序的启动时间,提高用户体验。

需要注意的是,动态库也有一些潜在的问题,如不同版本的动态库之间的兼容性、动态库的加载和链接时间等。因此,在使用动态库时需要注意版本管理和依赖管理,以确保程序的稳定性和可靠性。

总结来说,动态库是一种在程序运行时被动态加载到内存中的库文件,它具有共享性灵活性性能优化等特点。通过使用动态库,可以提高代码重用性、减少内存占用,并实现动态更新和升级。

动态库优缺点:

优点:

  • 更加节省内存并减少页面交换;
  • 库文件与程序文件独立,只要输出接口不变,更换库文件不会对程序文件造成任何影响,因而极大地提高了可维护性和可扩展性;
  • 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个库函数;
  • 适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。

缺点:

  • 运行时依赖,否则找不到库文件就会运行失败
  • 运行加载速度相较静态库慢一些
  • 需要对库版本之间的兼容性做出更多处理

👉🏻写个静态库

ar命令

ar 命令是Linux操作系统中的一个命令行工具,用于创建、修改和管理静态库。静态库是一组已经编译好的对象文件(.o文件)的集合,可以供其他程序链接使用。

ar 命令的基本语法格式如下:

ar [options] archive file...

其中,archive 表示要操作的静态库文件名,file... 表示要加入或删除的目标文件名列表。

ar 命令支持的常用选项包括:

  • r:将目标文件添加到静态库中,如果静态库中已经存在同名文件,则替换;
  • d:从静态库中删除目标文件;
  • t:显示静态库中包含的文件列表;
  • x:从静态库中提取指定的文件;
  • s:在静态库中创建符号表。

例如,要将两个目标文件 foo.obar.o 添加到名为 libfoo.a 的静态库中,可以使用以下命令:

ar rcs libfoo.a foo.o bar.o

其中,r 选项表示添加文件到静态库中,c 选项表示创建静态库(如果不存在的话),s 选项表示创建符号表。

要从静态库中删除一个目标文件 foo.o,可以使用以下命令:

ar d libfoo.a foo.o

要在静态库中提取一个文件 foo.o,可以使用以下命令:

ar x libfoo.a foo.o

注意:库真正的名字要去掉前面的lib和后缀.a

ldd 命令

ldd 命令是 Linux 上的一个用于查看可执行文件或共享库文件所依赖的动态链接库的工具。它的全称是 “List Dynamic Dependencies”。

ldd 命令的基本语法如下:

ldd [options] <executable_file>

其中,executable_file 是要检查依赖库的可执行文件或共享库文件。

ldd 命令会列出指定可执行文件或共享库文件所依赖的动态链接库列表。对于每个依赖项,它会显示库文件的路径,以及该库在运行时是否可以找到。如果库文件路径前面有 => 符号,表示该库文件被找到并加载了。

ldd 命令还支持一些选项,常用的选项包括:

  • -v:显示详细的信息,包括版本和符号表等。
  • -u:显示未使用的直接依赖项。
  • -r:递归地检查所有依赖项的依赖关系。
  • -d:显示输出的依赖项的调试信息。
  • -f:对于符号链接,显示链接的文件名和符号链接目标的路径。

例如,要查看可执行文件 myprogram 所依赖的动态链接库,可以使用以下命令:

ldd myprogram

ldd 命令会列出该可执行文件所依赖的所有动态链接库,并显示它们的路径和状态。

需要注意的是,ldd 命令只能查看动态链接库的依赖关系,对于静态链接库不起作用。另外,在某些情况下,ldd 命令无法正确识别某些依赖关系,因此在使用时需要注意其局限性。

第三方库和打包库

我们自己写的第三方库对于编译器来说,编译器是不认识的

像c库这种的第一方库就在系统的默认静态库目录中,编译器默认从那里找

编译器会找不到我们第三方库中的文件,那么我们在gcc编译时该怎么做呢?这里我们复习一下gcc的命令选项

下面是 gcc 常用的一些命令选项:

  • -c:只编译源文件,生成目标文件。
  • -o <file>:指定输出文件的名称。
  • -Wall:启用所有常见的警告信息。
  • -Wextra:启用额外的警告信息。
  • -g:生成调试信息,用于调试程序。
  • -O:优化代码,有不同的级别如 -O1-O2-O3
  • -I <dir>:指定头文件的搜索路径。
  • -L <dir>:指定库文件的搜索路径。
  • -l <library>:链接指定的库文件。
  • -D <macro>:定义一个宏。
  • -E:只进行预处理,生成预处理后的文件。
  • -S:只进行编译,生成汇编文件。
  • -shared:生成共享库文件。
  • -static:生成静态可执行文件。(必须静态链接,没有静态库直接报错)
  • -pthread:链接线程库。
  • -std=<standard>:指定使用的C语言标准,如 -std=c99-std=c11

这里我们主要用到 -l <library>:链接指定的库文件、-L <dir>:指定库文件的搜索路径、 -I <dir>:指定头文件的搜索路径。
在这里插入图片描述
如果我们要将自己写的库打包起来给别人,分为以下步骤:
1.gcc -c 目标.c文件命令将目标.c文件变为.o目标文件
2.使用ar命令将.o文件集合成静态库
3.至此我们要分别创建两个文件夹:lib文件夹和include文件夹,前者用来存放静态库.a文件,后者存放头文件.h文件
4.lib文件夹和include文件夹放在同一个文件目录底下,最后用tar czf 命令将文件压缩为zip文件,即打包完成
以下是一个示例
在这里插入图片描述
在这里插入图片描述

得到别人的第三方静态库怎么用

🔥方法一:将第三方库导入到系统默认库中

将第三方库文件中的lib 文件和include文件分别复制导系统默认的lib文件和include文件中

🔥方法二:使用gcc选项L,-l,-I
在这里插入图片描述
如果已经操作过方法一了,则-大i和-L可以不用。只需要指明链接的库名称即可

👉🏻生成动态库

  • shared: 表示生成共享库格式
  • fPIC:产生位置无关码(position independent code)
  • 库名规则:libxxx.so

示例:

[root@localhost linux]# gcc -fPIC -c sub.c add.c 
[root@localhost linux]# gcc -shared -o libmymath.so*.o 
[root@localhost linux]# ls add.c add.h add.o libmymath.so main.c sub.c sub.h sub.o

在这里插入图片描述

动态库的形成和使用与静态库其实都大差不差。
但是就是可执行程序时,系统会找不到动态库,因为动态链接时,系统只会在系统默认的lib和include文件下或者当前目录下进行查找(即使你告诉了编译器路径,但是此时已经跟编译器无关了,跟系统有关!!)。
在这里插入图片描述

得到别人的第三方动态库怎么用

🔥方法一:将第三方库导入到系统默认库中

将第三方库文件中的lib 文件和include文件分别复制导系统默认的lib文件和include文件中

🔥方法二:建立软链接
系统你不是会在当前目录下找吗,那我就在当前目录下建立一个软链接,这样在当前目录下就可以找到对应的头文件和动态库
在这里插入图片描述
🔥方法三:添加环境变量
LD_LIBRARY_PATH 是一个环境变量,用于告诉动态链接器(ld.so)在加载动态链接库时要搜索的路径。当程序启动时,动态链接器会按照一定的顺序来搜索动态链接库,其中包括 $LD_LIBRARY_PATH 环境变量指定的路径。

如果某个动态链接库在默认的路径中找不到,可以设置 LD_LIBRARY_PATH 环境变量来指定其所在路径。例如,假设我们编译了一个程序 myprogram,它依赖于一个共享库 libmylibrary.so,但该库不在标准路径中,我们可以通过以下命令来运行程序:

export LD_LIBRARY_PATH=/path/to/library:$LD_LIBRARY_PATH
./myprogram

这样,当程序运行时,动态链接器会首先在 /path/to/library 目录中搜索共享库 libmylibrary.so,如果找到则加载该库。

需要注意的是,LD_LIBRARY_PATH 环境变量只对当前 shell 有效,如果要使其对所有 shell 会话都有效,应该将其添加到 shell 的初始化文件中(如 ~/.bashrc/etc/profile 等)。另外,由于 LD_LIBRARY_PATH 可以被恶意用户滥用,因此在设置时需要谨慎,确保只添加必要的目录。

🔥方法四:直接更改系统关于动态库的配置文件
用sudo权限在/etc/ld.so.conf.d/目录下创建一个.conf文件,在文件中粘贴进去你动态库的位置路径即可。

如果没有生效可用ldconfig命令刷新一下
在这里插入图片描述

同一组库中既提供了动态库又提供了静态库

同一组库中既提供了动态库又提供了静态库,gcc默认使用动态库

使用外部库

在这里插入图片描述

👉🏻动态库加载

在 Linux 中,可执行文件和共享库都使用 ELF(Executable and Linkable Format)格式。ELF 格式是一种与平台无关的二进制格式,它包含了程序的代码、数据和元信息等内容,并且可以在编译时和运行时进行链接。

ELF 文件由三个部分组成:头部节区表节区数据。其中,头部包含了 ELF 文件的基本信息,如文件类型目标机器体系结构入口地址程序头表节区头表等;节区表则包含了各个节区的描述信息,如名字、偏移量、大小、属性等;节区数据则包含了实际的代码、数据和符号信息等。

对于可执行程序,ELF 文件中的代码和数据是直接可执行的,而符号表则包含了程序中定义和引用的所有符号,如函数名、全局变量名等。当程序被加载到内存中后,动态链接器会根据符号表中的信息来解析并加载依赖的共享库,并修复程序中的符号引用。最终,程序就能够正常运行了。

动态链接的程序,不光光自己要加载,链接的库也要加载

需要注意的是,不同的操作系统和体系结构可能使用不同的 ELF 格式版本,因此在跨平台或跨架构编译时需要进行相应的调整和处理。此外,由于 ELF 文件包含了大量的元信息,因此它的大小通常比较大,这也是一些嵌入式设备和资源受限系统不适合使用 ELF 格式的原因之一。

🌔一些拓展知识:

  • 程序没有被加载,程序内部有地址吗?答:有的,但注意是虚拟地址(逻辑地址:基地址+偏移量)
    在这里插入图片描述

  • 变量名、函数名等,编译成为2进制,还有地址吗? 答:没有

  • 编译的时候,对代码进行编址,基本遵守虚拟地址空间的那一套。虚拟地址空间,不仅仅时OS里面的概念,编译器编译的时候也按照这个规则进行编译可执行程序,这样才能在加载的时候,进行从磁盘文件到内存的映射

绝对编址和相对编址

绝对编址和相对编址是计算机体系结构中用于指令和数据访问的两种不同方式。

  1. 绝对编址(Absolute Addressing):
    在绝对编址中,指令或数据的地址是直接给出的物理地址。也就是说,每个内存单元都有一个唯一的物理地址,程序中使用的地址就是实际的物理地址。当程序执行时,处理器直接使用这些实际的物理地址来访问内存中的指令和数据。绝对编址通常用于固定的内存布局,例如裸机编程或实模式下的操作系统。

  2. 相对编址(Relative Addressing):
    在相对编址中,指令或数据的地址是相对于某个参考点的偏移量。这个参考点可以是当前指令的地址、当前指令所在的段基址、或者其他指定的基址寄存器。当程序执行时,处理器将相对地址与基址相加来计算内存中的实际地址,然后再访问指令和数据。相对编址通常用于采用分段或分页机制的操作系统,可以提供更大的地址空间,并且可以灵活地将不连续的物理内存映射到连续的逻辑地址空间中。

绝对编址和相对编址各有其特点:

  • 绝对编址可以提供直接访问物理地址的能力,适用于简单的嵌入式系统或需要直接操作硬件的场景。
  • 相对编址可以提供更大的地址空间和灵活的重定位能力,适用于复杂的操作系统和需要更大地址空间的场景。

需要注意的是,不同的计算机体系结构和操作系统可能使用不同的地址编址方式,因此在开发和移植程序时需要考虑和适配相应的编址方式。

动态库链接

库被加载之后,要被映射到指定使用了该库的进程的地址空间中的共享区部分。

动态库因为采用相对编址,所以加载到共享区任一地方都行,这如何理解?
1.在进程中,执行磁盘中的可执行程序
2.编译完成后,将进行动态链接动态库
3.我们知道动态库的起始地址,只要确定了库的起始地址(一旦库加载后,库在进程地址空间的位置就是确定的),库中的方法我们采用相对编址(相对库的起始地址),就可以在进程地址空间中的共享区分配到位置
4.而后再映射到页表中,反映到物理内存中
在这里插入图片描述

采用这种相对编址法的好处就是,若多个可执行程序调用同一个库,我们称其为共享库,因为库的起始地址都相同,所以在不同进程中的进程地址空间中,共享库在共享区中的位置都是相同的,调用的方法的偏移量也是相同的,如果该库已经被先加载了,只需要在映射页表中找到对应的物理内存地址即可。


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长

在这里插入图片描述
在这里插入图片描述

  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值