当前位置: 首页 > news >正文

Linux库——库的制作和原理(1)_回顾动静态库、制作使用库

文章目录

  • 库的制作和使用
    • 库的理解、制作和使用
      • 回顾动静态库
      • 理解库的本质
      • 静态库
        • 静态库的制作
        • 静态库的使用
        • 使用Makefile进行制作库
      • gcc/g++选项
      • 动态库
        • 动态库的制作
        • 动态库的使用差别
        • 正确使用动态库的方法
      • 动静态库同时存在的情况

库的制作和使用

本篇文章的内容是:库的制作使用。我们将重点分为以下几个方面:

1.回顾动静态库
2.理解库的本质,制作动静态库

接下来,我们将一起来对这些内容进行理解和学习。

库的理解、制作和使用

首先,我们先来了解本篇文章的第一个部分——库的理解、使用和制作。

回顾动静态库

在学习Linux下基础开发工具的时候,就已经初步地认识了动静态库了:
具体文章参考:Linux基础开发工具——gcc/g++及初步认识动静态库

首先我们理解了为什么要有库:即为了开发的方便!
像是一些代码中会常用的功能(如显示器的IO操作),这些要用户自己来写是有一定的难度,但是又需要大量使用,所以就有工程师专门的针对于这些常用的方法进行开发,打包成一个库。

其次是了解了动静态库的区别:
静态库是把库中内容拷贝到可执行文件中!动态库也称共享库,运行时会跳转到库中执行。


动态库是运行时跳转到库中进行执行,动态库在整个系统中也只会存在一份!这种方式可以大大减少可执行文件的体积。但是如果动态库损坏或者丢失就无法运行依赖该库的代码。

静态库会把内容全部拷贝到可执行文件中,这种方式稳定,且只要链接过该库一次,后序就不会再依赖于该库。但是带来的问题则是:容易导致文件体积过大!

接下来,我们将以过往对动静态库的认识为基础,来学习动静态库中更深层次的相关内容。

理解库的本质

库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从开始,因此库的存在意义非同寻常。

其实库就是可执行代码的二进制形式:

动态库 : .so[Linux] .dll[Windows]
静态库 : .a[Linux] .lib[Windows]

我们来看一下Linux下的一些常用的库:
在这里插入图片描述
使用指令ldd可以查看二进制可执行程序(指令的本质也就是可执行文件)依赖的库。

我们发现:
1.大部分依赖的库,都是.so为后缀的,即Linux下的动态库。
这说明系统偏向使用动态库。

2.大部分指令都会依赖一个叫做libc.so.6的动态库,其实这是一个c语言的标准库。

3.库的命名(无论是动态库/静态库),都会满足一个标准:
lib(库的名称) (.a / .so),即所有的库的名称,都应该以lib开头,然后紧跟着的就是该库的名称和后缀(取决于该库为动态库还是静态库)。
eg:libc.so.6,其实这个库真正的名称是c,是一个动态库!


接下来我们要输出一个结论:
即动静态库,是一个可执行代码的二进制形式,其实就是一些.o的目标文件的结合!

假设当前目录下有如下文件:

mystdio.h
mystring.h
mystdio.c
mystring.c
mystdio.o
mystring.o
usercode.c
usercode.o

其中,usercode.c是用户自己执行的代码。

按照工程上习惯的做法,把所有的.c文件编译成.o目标文件,然后再把所有的目标文件进行链接,就可以生成一个可执行文件。

但是,如果不想让当前目录存在过多的.o文件,是否可以这样做:
把用户使用的代码(有main函数的),之外的源文件,通通编译生成.o文件后,然后再以某种方式进行打包,形成一个整体。最后要生成可执行程序的时候,就让用户代码生成的目标文件和打包的整体再链接。

答案是:当然可以!
这就是库的本质——多个目标文件的打包,只不过这种打包方式并不是压缩文件的打包。


我们要知道
一般来说,有些方法的实现会放在一些特定的源文件中。但是实现各类方法的文件会有很多,所以工程师们会把这些带有接口实现的文件编译成.o文件,然后将这些接口实现的目标文件以某种格式进行打包,这样,就完成了一个带有实现接口的库!
然后通过用户自己写的代码,编译生成目标文件,再与库进行链接(这个库可能是动态/静态),这样子,我们在代码中就可以直接使用如printf这样的函数了。
Tips:打包方式先不谈,等讲解库的制作原理的时候进行讲解。

静态库

静态库的制作

首先,在制作动/静态库之前,我们需要再次理解一个问题:
即制作动静态库的时候,是否需要包含main函数呢?
答案是不需要!

因为动静态库的本质,其实就是一些是要调用方法/内容的集合。如果包装了main函数,到时候再和一个带有main函数的目标文件链接,那就出问题了。一个进程只能有一个main函数。


对于静态库的制作,我们需要使用工具ar,即archive,意思是对多个文件进行归档处理。所以,静态库的本质是多个目标文件的归档!

使用指令:ar -rc 静态库的名称 …(若干目标文件)(其中,名称是标准形式:lib().a)
其中r选项是replace的意思,意思是如果后序的目标文件中有某几个发生过更改,就对这个库中对应的部分进行替换。
c选项是create的意思,即如果后序目标文件中是不存在的,则添加到库中。

我们来尝试着使用一下:
就以在文件IO基础操作学习时,实现的IO操作接口进行打包
(会多生成一个mystring.h和mystring.c,然后再进行打包)

代码放在这里:
https://gitee.com/yangnp/linux-learn-code/tree/master/2025_7_25/my_IO_func

内容如下:

在这里插入图片描述
Makefile是自动化编译工具
有两个.c文件实现接口方法(mystdio.c mtstring.c)
有一个用户自行执行程序的代码(usercode.c)


但是现在,我们需要先把所有的.c文件编译成目标文件:
把此时的Makefile更名为Makefile_exe(因为后序需要使用Makefile打包制作库,至于进行编译的Makefile就先更名保存)。

在这里插入图片描述
使用ar -rc libmyc.a mystdio.o mystring.o,打包制作静态库:
在这里插入图片描述
此时,我们就完成了静态库的制作!

静态库的使用

接下来,我们需要学习如何使用静态库。

我们前面说到过,动静态库本质就是目标文件的集合罢了。所以,我们直接拿我们带有main函数的目标文件和库进行链接即可:
在这里插入图片描述
我们会发现,确实是可以。而且我们发现,我们的可执行程序mycode文件体积非常大,说明,静态库是把库中的内容拷贝到生成的可执行文件中的。


但是,我们一般不直接使用指令链接库:
而是让gcc这个工具在生成可执行程序的时候自己去找库:
在这里插入图片描述
如果不去寻找,那么带有main函数的代码在链接的时候就会报出链接错误!即找不到我们自己做的库!
(这里要说的是:我们自己写的这种属于第三方库,gcc在链接的时候只会在特定路径下去找对应的库,所以在当前目录下的库gcc找不到,所以链接不到对应的接口)。

对此,我们需要使用指令-l(库的名称),用来表示找什么库:如找libmyc.a -> -lmyc:
在这里插入图片描述
但是因为gcc不会在当前路径下自动寻找对应的库,所以需要使用选项-L,来告诉gcc还要去除了指定目录之外的哪个目录寻找库。

所以,最后就得到了一个mycode可执行文件,这个文件是可以正常使用的。

当然,有的人会说,为什么使用gcc的时候,有时候我们也没有去找标准的c库,为什么还能连接到标准的c库呢?
这是因为,gcc这个工具的默认行为就是要去找标准的c库,g++找标准c++库。这是gcc工具的默认行文!往后如果我们要使用非c/c++标准库,或者其它的一些第三方库(也可能是我们自己写的),都是需要使用-l去查找库。然后还要说明路径。


通过这种方式,其实很多时候不用给源代码也可以让程序跑起来的。比如我们下载的vs2022,其实不仅仅是下载软件本身,还下载了很多相关的库!这些库就是我们常用的头文件中函数的实现方法/或者相关内容。
我们是看不到这些库的源代码的!因为人家也不想让我们看到!

所以,最后我们在看vs 2022下载的内容的时候,我们会发现很多的.dllWindows下的动态库,还有对应的头文件。
其中,头文件是让用户查阅的文档!是能够让用户知道对应的内容和方法的使用。
还有一些库,这些库是用来进行链接的,以便于用户能够在自己的代码中直接使用!只不过是vs 2022这个IDE帮我们把所有的工作都做了。

所以,我们在这里就完成了一个lib文件夹静态库:
一般来说,系统下是把头文件放在include的目录下,库放在lib目录下。
在这里插入图片描述
当前目录下文件结构:
在这里插入图片描述
其实这里是不会有库的源码(mystdio.c mystring.c)的,只是为了后序操作起来方便才保留(因为制作动态库还用得到)。

我们现在再来试一下对usercode.c编译生成可执行文件:
在这里插入图片描述
竟然报错了,报错的原因是找不到头文件。

所以这里要说的是:
对于gcc/g++工具,它们会自动地到某个指定路径下找头文件,也会到某个指定路径下找对应的库二进制文件!
比如,gcc会在当前路径下或者系统指定路径下找头文件,会在指定路径下找库(不在当前路径下找库),所以,我们在这里没有指明头文件的查找位置也是出问题的。因为此时的头文件在文件夹lib,不在当前级目录下。

对此,解决方案是,使用选项-I,来指定头文件的查找路径:
gcc usercode.c -o mycode -lmyc -L ./lib/mylib -I ./lib/myinclude

在这里插入图片描述
至此,我们就明白如何正确地自己制作静态库,并且链接使用了。
但是,后序还是为了方便,在当前实验目录下,还是需要存放库的源文件和头文件。

使用Makefile进行制作库

但是,每次都要我们先编译再来链接制作库,是一件很麻烦的事情。所以,我们更希望能够把这件事情交给Makefile来做:

LIB=./lib/mylib 
INCLUDE=./lib/myinclude 
STORE=myc  libmyc.a:mystdio.o mystring.oar -rc $@ $^
%.o:%.cgcc -c $< .PHONY:
link:gcc -o mycode usercode.c -I $(INCLUDE) -l $(STORE) -L $(LIB).PHONY:
clean:rm -rf libmyc.a *.o mycode

其中,Makefile的默认指令是用来创建动态库。
当然,也可以直接使用make link进行链接已放在lib文件夹下的库进行生成可执行文件。
make clean用来清除当前目录下的一些文件。

gcc/g++选项

这里我们需要对上面出现的三个选项进行总结:
gcc -I 路径名 -> 表明要到指定路径查找头文件
gcc -l 路径名 -> 表明查的找库是什么(需要去掉前缀和后缀)(可能是动态,也可能是静态)
gcc -L 路径名 -> 表明要到指定路径查找库(可能是动态,也可能是静态)


后序还会出现一个选项,其实早已见过,即-static选项,即指定静态链接!
这个选项等下我们会再次地详细讲解。

动态库

接下来,我们来看看动态库的制作和使用。

动态库的制作

其实,动态库的本质是和静态库是一样的,但是,制作动态库的工具不一样。

我们直接使用gcc/g++即可制作动态库!
使用gcc/g++ -o 动态库名称(全称) …(目标文件) -shared即可生成动态库!
但是,这里需要说一个细节:
即生成.o目标文件的时候,需要使用一个选项-fPIC,即位置无关码!
这个原理我们目前是无法理解清楚的,等到我们讲库的加载原理的时候才能明白。

生成动态库在这里插入图片描述
直接与动态库链接在这里插入图片描述

动态库的使用差别

现在,同样的,我们把动态库拉到lib目录下:
在这里插入图片描述
然后我们直接对usercode.c进行链接,注意选项的使用:
在这里插入图片描述
我们会发现,可执行代码正常生成了,但是,运行的时候报错了:
报错原因 :error while loading shared libraries: libmyc.so: cannot open shared object file: No such file or directory -> 找不到动态库libmyc.so
在这里插入图片描述

这是一个很奇怪的现象,为什么会这样呢?为什么静态库这么使用是没有错的呢?

首先来解释这个的原因是什么:
1.动态库加载的的时候,进程只会去指定的目录下查找动态库!不会在当前路径下查找。
2.虽然已经把头文件、库名称、库的位置告诉gcc了,但是,这仅仅只是告诉了gcc。gcc可以找到对应的库和头文件生成可执行程序。真正执行程序的,是系统内的进程!进程只会在指定目录查找这个叫做myc的动态库。发现没有找到,所以报错。
3.静态库没有这问题是因为:
静态库是在链接的时候,就把内容加载到可执行程序里面了!所以运行程序的时候,使用的库的内容就是直接使用可执行文件内的库的方法。
但是动态库不一样,它不会加载到可执行文件中,而是执行到对应的方法后,跳转进入到动态库里执行代码。但是跳转的地方是需要查找的——系统指定目录。当前动态库是在lib下,所以运行代码的时候找不到是很自然的。

正确使用动态库的方法

首先,为了制作动态库的方便,还是应该使用Makefile来制作动态库:

LIB=./lib/mylib_dynamic 
INCLUDE=./lib/myinclude 
STORE=myc  libmyc.so:mystdio.o mystring.ogcc -o $@ $^ -shared
%.o:%.cgcc -fPIC -c $< .PHONY:
clean:rm -rf libmyc.so *.o mycode 

然后,我们现在需要提供多种方法来解决动态库使用中出现的问题。


1.直接在指定路径下加入头文件和动态库
既然说,系统要在指定路径下找到对应的头文件和库,那么直接在路径下添加我们自己写的库和头文件不就可以了。
其实这就是库的安装的本质!就像我们安装软件都是把可执行程序复制到/usr/bin目录下一样,本质就是拷贝!

拷贝.so文件到系统共享库路径下, ⼀般指 /usr/lib、/usr/local/lib、/lib64 或者开
篇指明的库路径等。
头文件一般是存储在/usr/include, /usr/local/include路径下。

我们来试试看:
在这里插入图片描述
因为是在root用户的文件内操作,所以需要进行提权!

我们此时再来运行试一下看看:
在这里插入图片描述
但是这种方法不太建议,因为这样子做会污染系统中的库,我们自己写的东西肯定是会多多少少有些问题的。


2.在系统库内添加软链接
在这里插入图片描述
也是一样可以的。
但是前面两种方法都是修改了系统的库,所以,还有别的方法吗?


3.使用环境变量LD_LIBRARY_PATH
这个环境变量指向一个路径,系统调用库的话会去这里面查找。

但是,有些系统下可能没有这个环境变量:
在这里插入图片描述

但是不管怎么样,我们可以自己加入库的路径到这个变量上,然后让系统自己去这里面查找:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:路径(绝对路径)
(为了验证这个方法的可行性,需要把刚刚复制到系统路径下的库和头文件删除)

一定要记住,添加的路径名称是绝对路径。
在这里插入图片描述
最后我们发现,这个方法也是可以的。但是,环境变量只在本次bash进程有效。一旦退出bash
进程了,下一次再登录就无效了!因为环境变量是从配置文件里导入的!当然,我们也可以直接修改配置文件,但是需要使用shell脚本,这里就不写了。


4.ldconfig方案:配置/etc/ld.so.conf.d/ ,再ldconfig更新

在 Linux 系统中,/etc/ld.so.conf.d/ 是一个专门用于​​管理动态库(.so 文件)运行时搜索路径​​的目录。它的作用是让系统管理员或软件包可以灵活地添加自定义的库搜索路径,而无需直接修改全局配置文件 /etc/ld.so.conf
在这里插入图片描述
里面的内容就是动态库所在的路径!


所以,我们可以添加一个我们自己的.conf文件,然后将库的所在路径写入文件中,这样子就可以让系统找到库了。

在这里插入图片描述
这里添加了一个my_2025_7_25.conf

然后把动态库路径写入,但是,这里是经过配置的。使用sudo都无法进行重定向。vim中也不能修改,因为设置了只读保护:
在这里插入图片描述


但是,可以使用nano记事本打开来写:
在这里插入图片描述
也是需要进行提权的。

然后最重要的一步是,需要使用指令ldconfig加载,否则无法生效:
在这里插入图片描述

加载后:
在这里插入图片描述
所以,使用这种方法是更加合适的!因为不需要直接修改系统中的库。也不需要每一次都导入环境变量。


方法总结:

1.将库和头文件拷贝至系统目录
库 -> /lib64 头文件 -> /usr/include
2.在系统库目录下创建软链接
3.将自己写的库的路径导入至环境变量LD_LIBRARY_PATH
4.配置文件:/etc/ld.so.conf.d/

动静态库同时存在的情况

当前,我们已经能够让系统找到我们自己写的库在哪里了。
但是,前面哦都是把动态库和静态库分开来放的, 如果放到一起,那么该运行哪一个呢?

把动静态库都放到mylib_together目录下:
在这里插入图片描述
重新生成可执行文件,查看链接方式:
在这里插入图片描述
我们会发现是dynamically linked,即动态链接。

所以,如果动静态库同时存在,gcc默认选择就是动态链接方式!其实很好猜出答案,因为可以直接使用gcc来制作动态库。


如果一定要静态链接,那就只能添加一个选项-static:
在这里插入图片描述
这样子就会变成statically linked,即静态链接。
但如果目录下只有静态库,那么gcc也只能选择静态的链接了。


在Linux系统下,绝大部分安装的库,都是动态库!
在这里插入图片描述
因为动态库的最大优点就是省空间!只要我们能够较好地保护这些动态库文件不被损坏和丢失即可,静态库如果加载到可执行程序里面,会导致程序过于臃肿!

而且,动态库只需要存储一份在指定路径下,就可以让多个应用程序使用这个库。这相比静态库的使用是节省了极大空间的。
我们也可以尝试着在vs 2022 IDE下尝试制作一个自己的库。这里就不演示了。

http://www.lryc.cn/news/599937.html

相关文章:

  • 上位机程序开发基础介绍
  • OpenCV结合深度学习进行图像分类
  • 练习实践-基础设施-文件共享-windows和linux之间的文件共享-smb服务搭建
  • 解决angular与jetty websocket 每30s自动断连的问题
  • 从kHz到GHz:晶振频率范围如何决定其应用场景
  • streamyfin(世博会)android 编译
  • 告别虚函数性能焦虑:深入剖析C++多态的现代设计模式
  • 萤石云替代产品摄像头方案萤石云不支持TCP本地连接-东方仙盟
  • 蓝光中的愧疚
  • Nacos-服务注册,服务发现(一)
  • 中级统计师-经济学基础知识-第七章 失业与通货膨胀理论
  • 怎么放大单片机输出电流
  • linux C — udp,tcp通信
  • 【硬件】LT3763中文手册
  • 51 单片机单文件多文件结构工程模板的创建教程
  • Nginx 安全加固:如何阻止 IP 直接访问,只允许域名访问
  • Linux网络配置全攻略:IP、路由与双机通信
  • freqtrade关于获取k线数量,以及显示时间的问题
  • JAVA知识点(六):性能调优与线上问题排查
  • Day 3: 机器学习进阶算法与集成学习
  • 【13】C# 窗体应用WinForm——.NET Framework、WinForm、工程创建、工具箱简介、窗体属性及创建
  • [ComfyUI] -入门2- 小白零基础搭建ComfyUI图像生成环境教程
  • 语义分割-FCN-听课记录
  • vue使用xlsx库导出excel
  • 零基础-动手学深度学习-6.1 从全连接层到卷积
  • 【高等数学】第五章 定积分——第四节 反常积分
  • DuoPlus云手机再上新:统一配置品牌型号、代理分组与便捷搜索功能全面提升!
  • zabbix服务自动发现、自动注册及配置钉钉告警(小白的“升级打怪”成长之路)
  • 2025年第四届创新杯(原钉钉杯)赛题浅析-助攻快速选题
  • Keepalived 原理及配置(高可用)