【Linux系统编程】:动态库和静态库

目录

1.动静态库的概念

2.深入理解静态库 

2.1制作静态库

2.2使用静态库 

安装静态库 

 3.深入理解动态库

3.1 制作动态库

3.2 解决加载器找不到动态库的方法

1.拷贝到系统默认的库路径下(/lib64/ or /usr/lib64/)

2.在系统默认的库路径下建立软链接

3.将自建库的路径添加到环境变量LD_LIBRARY_PATH中

4.在/etc/ld.so.cong.d/路径下建立自己的动态库路径的配置文件

 3.3 动态库是怎么被加载的

1.动静态库的概念

我们所说的动静态库本质上是函数库,函数库一般是由编程语言的标准实现提供的(称为标准库或基础库),或者是由第三方开发者创建的(称为第三方库)。

函数库通常是编译后的二进制文件或源代码文件,并在需要时被链接到应用程序中。

根据链接的方式,我们可以把函数库分为静态库(Static Library)和动态链接库(Dynamic Link Library,简称DLL)两大类,其中动态链接库又可称为共享库(Shared Library),简称动态库。

2.深入理解静态库 

2.1制作静态库

在类unix系统中Unix-like - Wikipedia,静态库一般命名为libXXX.a,其中,前缀“lib”表示函数库的意思,“.a”是静态库后缀,“XXX”是静态库的名称。

我们实现一个加减乘除的方法,其中方法的实现在源文件(mymath.c)中,方法的声明在头文件(mymath.h)中。

现在我们要把这个方法给别人使用,可以直接将源文件和头文件直接给别人,如果我们不想别人阅读源代码,可以将源代码打包成库,也就是将库+头文件给别人。

那么可不可以不给头文件只给库呢?

答案是不可以。不论库开不开源,都要有头文件,头文件相当于库的说明书,通过阅读头文件我们可以调用库中满足需求的函数。

如果我们不想别人看到我们的源代码,那么就可以将源代码和头文件打包成库:

我们创建一个Makefile文件,

Linux Manpages Online - man.cx manual pages

保存后make一下,

libmymath.a就制作好了,

现在我们需要将库和头文件整理到文件夹lib下的,

lib制作好了,别人怎么使用呢? 

2.2使用静态库 

我们来模拟一下,新建一个目录test,并将lib移动到当前目录下, 创建一个main.c文件,

然后编译它,发现编译错误,找不到mymath.h,这是为什么呢?

因为在预处理阶段,编译器需要对头文件进行展开,gcc会根据预处理指令#include"mymath.h",优先在当前工作路径下寻找指定的文件,如果没有找到,则会去编译器默认的头文件路径(/usr/include/)中寻找指定文件,

而这两种路径下都没有mymath.h,所以报错。

为了解决找不到头文件的问题,我们可以将#include"mymath.h"换成#include"./lib/include",主动给编译器提供文件的相对路径, 现在报错就不是因为找不到头文件了。这是在源文件中指定头文件的相对路径,还可以在编译时,主动让编译器在指定路径下寻找头文件,

gcc命令有个大写的"I"选项,可以指定寻找头文件的路径, 解决了找不到头文件的问题,现在又显示add未定义,这是为什么呢?

这是因为在链接时,gcc一般只会在默认的库路径下寻找被链接的库文件,而mymath.a不在默认的库路径下,就找不到mymath.a,即找不到add的定义。

gcc也给定了选项L,可以让我们指定链接库的路径,

我们指明了链接库所在的目录,为什么报错没有消失呢?

这是因为一个目录下可能有多个库,必须要告诉编译器链接的是哪个或哪些库,我们可以用选项-l指明链接库的名字。

-l后可以直接接库名,也可以带个空格,一般建议直接接库名。

为什么上面还是报错呢?

因为库的真正名字要去掉前缀和后缀, 问题1:为什么头文件只需要指明路径而不需要告诉编译器头文件名,而链接库需要告诉库名呢?

答;因为源文件中包含了头文件名,指定了头文件所在路径后编译器就可以根据头文件名寻找头文件,而源文件中不包含链接库名,所以在编译时需要特定地告诉编译器。

我们检测一下除法,发现除0后,errno打印结果不是1,这是为什么呢?

这是因为gcc编译器默认调用规范是_cdecl,参数是从右到左传递给函数,所以printf中,myerror先实例化成0,然后压入栈中,此时再调用div,修改后的myerror只能影响div后的语句。

 当然,我们也可以增加一个中间变量先接收div返回值,然后再打印,就不会出错了。

注意:gcc在链接时,默认采用动态链接,除非带上-static选项,则采用静态链接。

如果我们觉得编译的指令太长,也可以将头文件和库放在编译器默认的路径下, 

安装静态库 

安装库本质上就是将下载好的头文件和库分别放在系统默认的头文件和库文件的路径下,

注意:使用第三方库时一定要加库名。 

一般不建议将自己编写的库放在系统默认的库路径下,防止出现库污染。所以我们删除系统默认路径下自己编写的库文件和头文件, 我们还可以在系统默认路径下对库文件和头文件建立软链接,

取消软链接,

 3.深入理解动态库

3.1 制作动态库

 我们将mymath.h和mymath.c打包成了静态库,现在新建四个文件,将其打包成动态库,如下图所示,

跟静态库相似,先把所有的源文件编译成目标文件(.o文件),再将目标文件打包成库文件,加上头文件就组成了一个完整的库。

静态库是用指令ar打包的,动态库是用gcc直接打包的,

为什么动态库有可执行权限而静态库没有呢?

因为静态库是被链接到主函数中生成可执行文件,而动态库会被加载到内存中执行。 

一个文件有可执行权限就表示该文件能以可执行程序加载到内存中。动态库虽然没有main.c函数,但操作系统提供了动态链接机制来查找、加载和解析动态库。

上面是我们手动打包动态库。现在我们删掉打包产生的文件, 用makefile生成静态库和动态库并打包在一起,

假设别人下载了mylib库使用,

我们在test目录下模拟用户的使用,测试一下静态库,静态库成功编译。

我们试一下单独编译动态库, 发现找不到动态库,用ldd查看a.out依赖的共享库,发现找不到libmethod,可是我们不是告诉编译器了吗?

对的,我们是告诉了编译器,但动态库是在程序运行时被调用,我们没有告诉加载器,操作系统不知道加载什么库文件到内存中。

那为什么我们之前C程序可以直接调用动态库呢?

因为系统默认的动态库加载路径下,有C语言标准库。

要想让系统找到我们编写的动态库,有四种方法。 

3.2 解决加载器找不到动态库的方法

1.拷贝到系统默认的库路径下(/lib64/ or /usr/lib64/)

直接将库文件拷贝到系统默认的路径(/lib64/ or /usr/lib64/)下, 与静态库类似。

2.在系统默认的库路径下建立软链接

我们在系统默认的库文件路径下对libmethod.so建立软链接,我们直接运行a.out,

运行成功。 

我们取消软链接,编译就会报错。

3.将自建库的路径添加到环境变量LD_LIBRARY_PATH中

 LD_LIBRARY_PATH是一个环境变量,用于告诉系统动态链接器(dynamic linker/loader)在哪些目录下查找程序运行所需的动态链接库(shared libraries)。当可执行文件执行时,系统会首先搜索LD_LIBRARY_PATH中指定的路径,然后再搜索系统默认的库路径(如/lib、/usr/lib、/usr/local/lib等)。

 如上图所示,我们可以将自编库文件的路径添加到环境变量LD_LIBRARY_PATH中,此时操作系统就能找到库libmethod.so。

运行结果也没有问题。但这个环境变量只是暂时的,如果要永久性添加该环境变量,将`export`命令添加到用户的Shell配置文件中(如~/.bashrc或~/.bash_profile,“~”表示家目录),以实现每次启动Shell时自动设置LD_LIBRARY_PATH。例如:`echo 'export LD_LIBRARY_PATH=/path/to/library' >> ~/.bashrc`。然后,重新打开终端或使用`source ~/.bashrc`命令使设置生效。

4.在/etc/ld.so.cong.d/路径下建立自己的动态库路径的配置文件

/etc/ld.so.conf.d/是一个目录,它用于存放Linux系统中动态链接库(Dynamic Link Library)的配置文件。这些配置文件指定了动态链接器(dynamic loader)在运行时搜索动态库(.so文件)的目录。 

因为etc在根目录下,我们先切换到root账户,

打开ld.so.conf.d,新建一个配置文件yaols.conf。

注意:/etc/ld.so.conf.d目录下的每个配置文件都是一个简单的文本文件,其中包含了一个或多个目录路径。这些路径就是动态链接器在搜索动态库时应该查找的目录。例如,一个名为qt-x86_64.conf的配置文件可能包含如下内容: 

/usr/lib64/qt-3.3/lib

这表示动态链接器在搜索动态库时应该包括/usr/lib64/qt-3.3/lib这个目录。

所以我们将libmethod.so所在目录的路径添加到文本文件yaols.conf中, 

然后我们输入ldconfig命令,将添加的路径刷新到动态链接器的缓存文件(/etc/ld.so.cache)中。

/etc/ld.so.cache 文件是 Linux 系统中的一个重要文件,它用于存储动态链接器(dynamic linker/loader)在运行时可以找到的共享库(shared libraries)的路径。这个文件是由 ldconfig 命令生成的,它读取 /etc/ld.so.conf 文件(以及 /etc/ld.so.conf.d/ 目录下的配置文件)中指定的目录,然后扫描这些目录中的共享库文件,最后生成一个缓存文件,即 /etc/ld.so.cache

动态链接器是 Linux 系统中的一个组件,它的作用是在程序运行时,将程序所需的共享库加载到内存中。这样做的好处包括节省内存(因为多个程序可以共享同一个库的副本)和便于库的更新(只需更新库文件,无需重新编译依赖该库的程序)。

/etc/ld.so.cache 文件的存在提高了动态链接器查找共享库的效率,因为它避免了在每次程序启动时都扫描整个文件系统来查找库文件。相反,动态链接器可以直接从缓存文件中读取库文件的路径,从而快速加载所需的库。

如果系统中的共享库文件被更新或添加,或者 /etc/ld.so.conf 文件及其目录下的配置文件被修改,那么应该运行 ldconfig 命令来更新 /etc/ld.so.cache 文件,以确保动态链接器能够正确地找到所有共享库。

简而言之,/etc/ld.so.cache 文件是 Linux 系统中用于提高动态链接器查找和加载共享库效率的一个关键文件,它通过 ldconfig 命令根据 /etc/ld.so.conf 及其目录下的配置文件生成,并应在这些配置发生变化时及时更新。来源:文心一言

 虽然我们有四种方法让系统找到动态库,但一般我们安装库时都采用第一种方法。

上面都是将动态库和静态库分开编译,我们一起编译试一下,运行没有问题。

 3.3 动态库是怎么被加载的

动态库本质上是一个文件,理解了文件是如何被进程打开?如果从磁盘中加载到内存?就很容易理解动态库是怎么被进程加载的。

当一个进程需要调用某个动态库中的函数时,会优先在内存中寻找该动态库,或者说是在进程地址空间的共享区中寻找动态库,如果找到了,就直接调用;没找到,则需要从磁盘中加载该动态库。

当动态库被加载到进程的共享区时,也就是在内存中,如果另一个进程也需要调用该动态库的某个函数,可以直接访问共享区中已经加载好了的动态库。注意:共享库的数据可以被多个进程访问。

我们知道C语言标准库里有个错误码,是个全局变量,当多个进程使用C库中的错误码errno时,不会冲突吗?共享区是虚拟内存空间,是对一块物理内存的映射,当多个进程需要访问同一个动态库会有一系列的同步机制(如互斥锁、信号量、条件变量等)来避免竞争条件和数据不一致,如果某个进程修改了库中的数据,可以“写时拷贝”重新拷贝一份动态库(在物理内存中重新分配一块空间存储修改的数据),并不会冲突。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值