【Linux】基础I/O——动静态库的制作

我想把我写的头文件和源文件给别人用

  • 1.把源代码直接给他
  • 2.把我们的源代码想办法打包为库

1.制作静态库

1.1.制作静态库的过程

我们先看看怎么制作静态库的!

makefile

所谓制作静态库

  1. 需要将所有的.c源文件都编译为(.o)目标文件。
  2. 使用ar指令将所有目标文件打包为静态库。
  3. 将打包成的静态库需要和头文件组织起来。
  • 1.需要将所有的.c源文件都编译为目标文件。
cpp 复制代码
gcc -c mymath.c

-c选项告诉gcc只编译源代码,但不进行链接。这会生成一个目标文件(通常以.o结尾),该文件包含了编译后的代码,但还不能直接运行。

你可以使用-c选项来编译多个源文件,然后再使用链接器将它们链接成一个可执行文件或共享库。

那么如果我们只把.o和.h文件给别人,别人能用吗?

我们写一个程序

main.c编译:

复制代码
gcc main.c -c

然后,将main.o和其他.o文件链接以后生成的文件就是可执行程序:

是可以运行的

通过上面的例子我们知道,需要将生成的所有目标文件和main.o文件链接才能生成可执行程序,**但是除了main.o之外的.o文件都太分散了,用起来很麻烦(当然可以通过Makefile简化步骤),给别人使用也不太方便,还容易缺失,所以将它们打包。**而将目标文件打包的结果就是一个静态库。

  • 2.使用ar指令将所有目标文件打包为静态库。

ar 命令是 GNU Binutils 的一员,可以用来创建、修改静态库,也可以从静态库中提取单个模块。它可以将一个或多个指定的文件并入单个写成 ar 压缩文档格式的压缩文档文件。

常用参数:

  • -r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。
  • -c(create):建立静态库文件。
  • -t:列出静态库中的文件。
  • -v(verbose):显示详细的信息。
cpp 复制代码
语法:ar [选项] [库名] [依赖文件]

例如,将mymath.o打包:

-t-v选项查看静态库中的文件及信息:

我在这里想先声明一下库的名字这个问题

  1. 静态库的名字一般为libxxx.a,其中xxx是该lib的名称。
  2. 动态库的名字一般为libxxx.so.major.minor,xxx是该lib的名称,major是主版本号, minor是副版本号。

我们给库取名字的时候可要注意这个前缀后缀的事情啊!!!!

  • 3.将打包成的静态库需要和头文件组织起来。

**这是因为头文件包含了静态库中函数和变量的声明,而这些声明对于使用静态库的程序来说是必要的。**如果没有头文件,编译器将无法确定如何使用静态库中的函数和变量。

**头文件和函数的实现分离是为了提高代码的可维护性和可重用性。**头文件中只包含函数和变量的声明,而不包含具体的实现。这样,当我们需要修改函数的实现时,只需要修改对应的源文件,而不需要修改头文件。同时,由于头文件只包含声明,因此可以被多个源文件共享。这样,当我们需要在多个源文件中使用同一个函数时,只需要在每个源文件中包含对应的头文件即可。

**组织静态库和头文件的方法有很多种。一种常见的方法是将静态库文件(.a文件)和头文件放在同一个目录下。**在使用静态库时,需要在程序中包含对应的头文件,并在编译时指定静态库的位置。这样,编译器就能够找到静态库中的函数和变量,并将它们链接到程序中。

例如,将所有的头文件(.h)放在一个名为include的目录下,将生成的静态库文件(.a)放在一个名为lib**(自己创建的)**的目录下。然后将这两个目录都放在名为lib的目录下,这个libtest就可以作为一个第三方库被使用。

至此我们的库就已经完成了

1.2.makefile完成库的创建

上面的步骤其实我们可以一步到位

lib文件就是我们创建的静态库 ,未来别人想用我们的库,就把lib文件给它就好了。

1.3.使用静态库

我们来演示一下

我们创建一个新的.c文件

现在这个写好的main.c,让它和含有头文件、静态库文件的lib共处同一目录B下才能调用库中写好的函数。

我们现在编译

报错了,它说没找到头文件!!!!

为什么?

因为gcc会在默认的路径寻找(/usr/include),我们这个mymath.c不在系统目录下面,那么就找不到了

我们可以使用gcc -I(大写i)来指定寻找的路径去找头文件

  • -I(大写i):指定查询头文件路径

还报错了!!!!

这个是因为它们没有找到add函数,这个就是没有找到方法实现,即没找到库文件了,因为****实现在库里面

这是因为gcc只会在默认路径下面寻找静态库(/lib64/),但是我们这个库可不在那个目录里面

这个就需要加选项-L选项来给gcc指定查询路线**:**

  • -L:指定静态库位置

还报错,显示显示没找到库文件!!!

明明我这个目录下面只有1个库,还是不链接起来,这个就是库的链接方式不同之处:库需要库的位置+库的名字

这个时候还要加选项:-l:链接对应的库

??????什么鬼,还是找不到,其实这是我们的库的名字搞错了

库的真实名字是去掉库的前缀,后缀之后的东西

Linux下:一般要求是lib + 库的真实名称 +(版本号)+ .so /.a

所以这个库的真实名字就是mymath

我们先看看我们设置的自定义环境变量

我们改一下代码

出错了???为什么这里没有将myerror设置为1?

这个是因为printf传参是从最右边开始传的,打印myerror的语句在调用div之前就已经准备好了

我们可以来验证一下

怎么样,是不是很神奇?那我还是想先打印10/0怎么办?

很好

这个就是c语言的errno全局变量的简单类比!!!

至此,我们就学了这些东西

  1. 第三方库往后使用的时候必定要使用gcc -l(小写L)
  2. 深刻理解errno的本质

我们这么知道某个程序是使用动态链接还是静态链接的方式呢?

为什么我们没有看到mymath这个库?

gcc默认动态链接,但是我们没有提供动态库,只提供静态库,所以只能用静态链接

如果系统中需要链接多个库,则gcc可以链接多个库

来总结一下吧

1.4.使用库的便捷方法

现在终于完成了,我现在不想带这么多选项,怎么办?

  1. 把库拷贝到**/lib64/,把头文件拷贝到/usr/include**
  2. 我们可以创建软连接来解决这个问题

我们可以这么干

  • 我们把头文件和库文件拷贝到系统指定目录下面,就可以不用带这么多选项了

拷贝完成之后,我们执行一下这个

**发现是链接错误,我们就得加上-l选项,**这个名字注意啦!!!!

我们将库的拷贝,头文件的拷贝这个过程叫作安装!!!!

我们先把安装进去的删掉

第二种方法就是使用软链接

从此往后我们使用这个头文件的时候就像下面这样子

我们也给我们的库创建一个软链接

我们现在编译main.c

很成功

来总结一下

首先我们在/usr/inuclde/里面创建了一个软连接,我们在头文件写了这样子的东西,就完成了对头文件的链接

上面这个相当于下面这个-I选项对头文件的链接

cpp 复制代码
 -I ./lib/include/ 

然后我们在/usr/include/里面创建一个软链接,然后库的链接还是需要指定库的名字的!!!所以最后那个-l+库名不可缺少

cpp 复制代码
-L ./lib/mymathlib/ -l mymath

但实际上我们这样子用软链接的情况很少

1.5.总结

  1. 需要指定的头文件,和库文件
  2. 如果没有默认安装到系统gcc、g++默认的搜索路径下,用户必须指明对应的选项,告知编译器: a.头文件在哪里 b.库文件在哪里 c.库文件具体是谁
  3. 将我们下载下来的库和头文件,拷贝到系统默认路径下,在Linux下就是安装库! 那么卸载呢?对任何软件而言,安装和卸载的本质就是拷贝到系统特定的路径下!
  4. 如果我们安装的库是第三方的库,我们要正常使用,即便是已经全部安装到了系统中,gcc g++必须用-l指明具体库的名称!

2.制作动态库

2.1.制作动态库的过程

这次我们要使用新文件来创建

myprint.h

myprint.c

mylog.h

mylog.c

我们要使用这几个来打包成动态库

制作静态库有3个步骤

  • 1.生成所有源文件(.c文件)对应的目标文件(.o文件)。
  • 2.使用 gcc 的 -shared 选项将所有目标文件打包为一个动态库。
  • 3.组织头文件和动态库文件。
  • 1.生成所有源文件(.c文件)对应的目标文件(.o文件)。

gcc 需要增加-fPIC选项(position independent code):位置无关码。这个我们下面讲讲

cpp 复制代码
gcc -fPIC -c mylog.c 
gcc -fPIC -c myprint.c 

-c选项告诉gcc只编译源代码,但不进行链接。这会生成一个目标文件(通常以.o结尾),该文件包含了编译后的代码,但还不能直接运行。

你可以使用-c选项来编译多个源文件,然后再使用链接器将它们链接成一个可执行文件或共享库。

位置无关码

位置无关代码(Position Independent Code,PIC)是一种特殊的机器代码,它可以在内存中的任何位置运行,而不需要重新定位。这意味着,当程序被加载到内存中时,它的代码段可以被放置在任何可用的内存地址,而不需要修改代码中的任何地址引用。

这对于创建共享库(即动态库)非常有用,因为共享库可以被多个程序同时使用,而每个程序都可能将其加载到不同的内存地址。如果共享库中的代码不是位置无关的,那么每次加载时都需要对其进行重新定位,这会增加程序启动的时间和内存占用。

使用位置无关代码可以避免这些问题,因为它可以在内存中的任何位置运行,而不需要重新定位。这样,当多个程序使用同一个共享库时,它们都可以直接使用共享库中的代码,而不需要对其进行重新定位。这样可以节省大量的 RAM,因为共享库的代码节只需加载到内存一次,然后映射到许多进程的虚拟内存中。

和静态库采用的绝对编址相比,动态库采用的就是相对编址,各个模块在库中的地址可能不相同,但是它们之间的相对位置是固定的。就好像房车旅行一样,房车的位置虽然一直在变,但是房车内家具的相对位置一直不变。

位置无关代码对于 gcc 来说:

  • -fPIC作用于编译阶段,告诉编译器产生与位置无关的代码,此时产生的代码中没有绝对地址,全部都使用相对地址,从而代码可以被加载器加载到内存的任意位置都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
  • 如果不加-fPIC选项,则加载. so 文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个. so 文件代码段的进程在内核里都会生成这个. so 文件代码段的拷贝,并且每个拷贝都不一样,这样就和动态库一样占用内存了,具体取决于这个. so 文件代码段和数据段内存映射的位置。
  • 不加-fPIC编译生成的. so 文件是要在加载时根据加载到的位置再次重定位的,因为它里面的代码 BBS 位置无关代码。如果该. so 文件被多个应用程序共同使用,那么它们必须每个程序维护一份. so 的代码副本 (因为. so 被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)。
  • 我们总是用-fPIC来生成. so,但从来不用-fPIC来生成. a。但是. so 一样可以不用-fPIC选项进行编译,只是这样的. so 必须要在加载到用户程序的地址空间时重定向所有表目。
  • 2.使用 gcc 的 -shared 选项将所有目标文件打包为一个动态库。
cpp 复制代码
gcc -shared -o libmymethod.so mylog.o myprint.o

其中,在选项-o后面的是要生成动态库的名称,在它之后是动态库依赖的目标文件。

这个库文件有了x权限,是因为这个动态库被使用的时候会被加载到内存里,所以默认有x权限

  • 3.组织头文件和动态库文件。

同样地,将所有的头文件(.h)放在一个名为include的目录下,将生成的静态库文件(.a)放在一个名为lib的目录下。然后将这两个目录都放在名为mylib的目录下,这个mylib就可以作为一个第三方库被使用。

2.2.使用makefile制作动态库加静态库

上面那个步骤太繁琐了,我们借助makefile来看看,我们这里还有之前的静态库生成代码

我们使用make完成第1步和第2步,make out就可以完成上面的第3步

2.3.使用动态库

我们将我们的库给了别人

我们编写一个main函数来使用

先看看里面的静态库能不能用

很好啊,能用

我们接下来修改我们的main函数

找不到头文件

这个是说找不到库的实现, 这个和静态库一样

如果我们有多个库就得多使用多个选项去选就好了

还是报错,这个和静态库一样,还是要指明链接库

我们生成了,但是运行起来的时候怎么报错了???

这个和静态库就不一样了啊

这个a.out就是动态链接的 ,那为什么ldd的第二行为什么说not found呢?

我已经告诉编译器(gcc)库的路径了,但是运行的时候是加载器来运行的,所以还是要告诉加载器

我们以前运行c语言程序的时候,为什么能直接执行呢?

  • 这个是因为系统默认了加载路径,LD_LIBRARY_PATH是程序运行动态查找库时所要搜索的路径

接下来我们将介绍4种解决方法

2.3.1.将库拷贝到系统目录------最常用的

类似静态库的操作:

cpp 复制代码
sudo cp mylib/lib/libmymethod.so /lib64

现在这个动态库就被找到了。

但是,为什么只要将动态库的.so文件拷贝到系统目录下,这个可执行程序就可以被链接到动态库呢?不应该重新编译链接一次吗?

在编译链接时,只需要记录需要链接文件的编号,**运行程序时才会进行真正的"链接",所以称为"动态链接"。**因此,只要将动态库的.so文件拷贝到系统目录下,这个可执行程序就可以被链接到动态库,而不需要重新编译链接。

也就是说,编译器只负责生成一个main.c对应的二进制编码文件,而链接的工作要等到运行程序时才会进行链接,所以生成可执行程序以后就没有编译器的事了。

缺点:同样地,将动态库的.so文件拷贝到系统目录下也可能会污染系统库目录。

2.3.2.软链接

cpp 复制代码
sudo ln -s /home/zs_108/B/mylib/lib/libmymethod.so /lib64/libmymethod.so

我甚至不用编译就找到了静态库

运行看看

完美

一解除软链接。又找不到了 ,也运行不了了

2.3.3.更改LD_LIBRARY_PATH

LD_LIBRARY_PATH是程序运行动态查找库时所要搜索的路径

这个环境变量在有些系统是没有的!!!!

我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH环境变量中,告诉系统程序依赖的动态库所在的路径:

cpp 复制代码
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/xy/Linux/libtest/libtest/mylib/lib

我们设置好了,使用ldd就发现,直接又找到了

这个时候我们也可以运行a.out

注意要用:隔开,否则会覆盖原来的环境变量。但是这个方法是临时的,因为这个环境变量是内存级别的环境变量,机器会在下次登录时清理。

2.3.4.使用ldconfig指令

/etc/ld.so.conf.d/目录下的文件用来指定动态库搜索路径。

这些文件被包含在/etc/ld.so.conf文件中,ldconfig命令会在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索可共享的动态链接库,并创建出动态装入程序(ld.so)所需的连接和缓存文件。

这些.conf文件中存储的都是各种文件的路径,只要将我们写的第三方库的路径保存在一个.conf文件中,程序运行时在就会通过它链接到它依赖的动态库。

  • 1.将路径存放在.conf文件中。
cpp 复制代码
echo "/home/zs_108/B/mylib/lib" > mylib.conf

这样,当前目录下就会出现刚才创建的文件:

  • 将.conf文件拷贝到/etc/ld.so.conf.d/下。
cpp 复制代码
sudo cp mylib.conf /etc/ld.so.conf.d/

这个时候ldd一下:

系统还是没有找到a.out依赖的动态库,原因是此时的系统的数据库还未更新,使用命令ldconfig更新配置文件:

复制代码
sudo ldconfig

怎么样?是不是很方便!!!!

其实我们可以直接去**/etc/ld.so.conf.d/下创建一个.conf文件,往里面写入动态库路径也行**

这个比较神奇

删了还存在

我知道现在大家想试试看自己的能力

我给大家推荐一个库ncurses------图形化界面,大家可以去玩玩看

2.4.总结

我们现在修改main函数

这样子就大功告成了

我们现在把静态库删掉了,还是可以运行的

个就是静态链接的特点,会拷贝静态库的内容过来

这个时候我们把动态库删了,就真的不能跑了

这个就是动态链接的加载模式,运行的时候才加载动态库,如果这个时候动态库不见了,那么就运行不了了

常见的动态库会被所有的可执行程序(动态链接)使用,所以动态库也被叫做共享库

动态库只会被加载1次,动态库被加载后,会被所有进程共享!!!!

3.动态库是怎么被加载的?

我们先回答动态库是如何与虚拟以及物理内存,以及PCB建立关系的

先理解一下上图要表达的一个过程,顺便进行一些知识整合:当用户要加载一个进程时,操作系统就会为进程创建一个task_struct,并且把程序加载到内存中,并且会创建对应的mm_struct(虚拟地址空间)用来维护各个区域,最后再经过页表将虚拟地址和物理内存进行对应,这是可以理解的,也是前面已经提及到的内容

动态库是文件吗?

  • 答案是

但是库并不是立刻就被加载,而是在它需要被调用的时候才会被加载到内存中。

那么现在进程中会调用一些函数,这些函数会与多个动态库有联系,而我们知道,在可执行程序采用动态链接进行链接库的时候,会想办法让可执行程序与库建立联系,这个时候让动态库加载进来, 动态库会被加载到进程地址空间的堆和栈之间的这一块区域,也被叫做共享区,之后也会在页表中和物理地址建立对应的联系,库被加载后就可以被进程所用了

  • 把动态库加载到共享区之后,我们执行的任何代码都是在我们的进程地址空间中执行

我们看看进程地址空间的结构

这样子正文代码执行的时候就在正文代码区,执行到动态库的函数的时候就跳转到动态库的地方执行,执行完了又跳回正文代码区

事实:我们一个进程可能加载多个动态库,动态库相当于文件,动态库可以和多个进程,系统在运行中一定会存在多个动态库,一定会将它们先描述再阻止管理起来。

系统中,所有库的加载情况,操作系统特别清楚。

  • c语言会提供一个errno全局变量,是不是在共享库里,a进程打开运行,出错了,b进程打开运行也出错了,那么errno会不会混乱了?

库是在堆栈之间,0-3G是用户空间,里面运行的都是子进程,如果发生异常了,就会进行写时拷贝,不会出现问题

相关推荐
用户9718356334664 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪6 小时前
linux 拷贝文件或目录到指定的位置
linux
大树8821 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠21 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质1 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
小宇宙Zz1 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工1 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理