【Linux】动静态库

前言

本章将来学习一下动静态库的相关知识从认识动静态库到自己手写简单版本的库最后将库发布出去如何去使用第三库等方面做详细讲解。目标已经确定接下来就要搬好小板凳准备开讲了…


1. 认识动态库与静态库

在我们之前学习gcc工具的时候我们就简单了解过一些关于动静态库的相关知识 【动静态库复习 - 传送门】

  • 动态库Linux(. so) windows(. dll) 自己的程序中没有实现通过链接别人写好的库调用别的库中的实现。
  • 静态库Linux (. a) windows(. lib) 自己的程序中是将库中的相关代码直接拷贝到自己的可执行程序中

2. 生成动静态库

库里面需不需要main函数

库中是不能带main函数的因为用户写了main函数一旦把就出现main函数冲突所以库里面不要main函数。

如果我把我的所有的. o文件给别人别人能链接使用吗

  • 链接不就是把所有的.o文件链接形成一个可执行程序吗。
  • 而我们又知道只有目标文件是不能将程序执行起来的。
  • 必须要链接相应的库我们之前讲过系统中有对应的库供我们使用。
  • 所以有目标文件又有库经过链接就可以将程序执行起来。

在这里插入图片描述
上级目录的源文件mymath.c和myprint.c在当前目录下形成目标文件。

在这里插入图片描述
再将当前目录下的源文件test.c在当前目录下形成目标文件。
我们再讲这三个目标文件链接本地的库生成可执行程序a.out。

小结

  • 如果就是把所有的.o文件给别人别人能连接我的.o文件来使用这是可以的。
  • 把所有的源文件编译成.o文件再把这些.o文件给别人链接的时候将自己的源文件也变成.o文件然后将所有.o文件一链接就形成可执行了。
  • 链接的时候本质上就是将三个.o文件合起来形成一个可执行程序。
  • 这就叫做静态链接。

要用到的

2.1 静态库

上述我们讲了将多个目标文件合起来形成可执行程序。如果包含了几百上千个源文件在链接的时候容易丢失所以打包一下就可以了。

ar指令

在Linux中“ar” 命令的单词缩写是Archiver"。该命令用于创建和管理静态库(archive) 文件。它通常与其他编译工具如gcc一起使用用于将目标文件打包成一个静态库文件以供其他程序使用。

在这里插入图片描述
静态库的命名以llib开头 + 名字 + .a结束。

在这里插入图片描述
在这里插入图片描述
所以静态库就相当于源文件生成.o文件最后打包起来就可以了。
当别人想用我的时候就是在我的这个库里面找对应的.o文件拷贝到自己的的可执行程序当中。

2.1.1 静态库的发布

在这里插入图片描述
我们将所有的目标文件归档到静态库中将库放在一个文件夹里将所有的头文件打包放在一个文件夹里整体打包成一个静态库发布出去。

在这里插入图片描述

2.2 动态库

原理与静态库几乎一样区别就是要用到gcc -fPIC产生与位置无关码。

  • 大家编译时采用的地址空间策略是一样的都是以4GB的地址空间为单位来排布自己的代码和数据的。
  • 静态链接将自己形成的.o文件拷贝到可执行程序的地址空间范围的某个区域当中就可以了这种形成的代码叫做与位置有关。
  • .o代码不能在内存的任意位置去加载必须得是拷贝到程序里面在程序里面以地址空间的绝对地址的方案呈现在进程层面上让系统去调用。
  • 这就是myprint和mymath编译好之后采用的与地址有关码。
  • -fPIC与地址无关码将来编译的源代码内部的代码和将来将这两个.o文件加载到程序任意位置都可以让程序执行。采用的是起始地址 + 偏移量对的方式是一种相对地址的方案。

动态库的命名以llib开头 + 名字 + .so结束。

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

  • -shared表示生成共享库格式
  • -fPIC产生位置无关代码position independent code

2.2.1 动态库的发布

在这里插入图片描述
在这里插入图片描述
和上述静态库一样将头文件和库分别打包最后放在一个文件夹里。

2.3 一并发布

最后为了统一发布我们将动静态库统一打包

.PHONY:all
all:libmymath.so libmymath.a

libmymath.so:mymath.o myprint.o
		gcc -shared -o libmymath.so mymath.o myprint.o
mymath.o:mymath.c
		gcc -fPIC -c mymath.c -o mymath.o -std=c99
myprint.o:myprint.c
		gcc -fPIC -c myprint.c -o myprint.o -std=c99

#要注意避免文件名冲突问题
libmymath.a:mymath_s.o myprint_s.o
		ar -rc libmymath.a mymath_s.o myprint_s.o
mymath_s.o:mymath.c
		gcc -c mymath.c -o mymath_s.o -std=c99
myprint_s.o:myprint.c
		gcc -c myprint.c -o myprint_s.o -std=c99

.PHONY:lib 
lib:
		mkdir -p lib-static/lib
		mkdir -p lib-static/include
		cp *.a lib-static/lib
		cp *.h lib-static/include
		mkdir -p lib-dyl/lib 
		mkdir -p lib-dyl/include
		cp *.so lib-dyl/lib
		cp *.h lib-dyl/include

.PHONY:clean
clean:
		rm -rf *.o *.a *.so lib*

这样就能先make生成各种.o文件make lib分别生成动静态库。

看一下目录结构

在这里插入图片描述


3. 动静态库的使用

我们分别将动静态库拷贝到自己的目录中用自己的代码来试用一下它们。

3.1 静态库的使用

在这里插入图片描述
高亮是因为gcc没办法找到我们对应的头文件

  • " "是在当前路径下找。
  • < >是在库目录下面找。
  • 如何知道库中有哪些方法先看头文件里面有哪些头文件~

上述包头文件的方式肯定是不行的解决办法

  • 我们当然可以指定头文件的路径但是那样要写的路径太长了太麻烦了。
  • 将自己写的头文件导入到/usr/include/路径下再将自己的库拷贝到系统库文/usr/lib64/。云服务器都是/lib64
    • 不建议这么做会污染系统库。
  • 此时不再报头文件找不到的错误而是报了链接错误。
  • 我们之前没有用过第三方库gcc和g++默认就认识C/C+ +的库所以不用指定链哪个库
  • 但是现在链接执行可执行必须要指定链接哪个库。
gcc mytest.c -lmymath
  • -l指明了要link什么库。
  • 之前是gcc和g++默认会带上链接系统库而我们写的是第三方库。
  • 需要我们手动链接库。

光有这个还是不行的我们还要指明静态库和头文件的搜索路径正确使用静态库方法

gcc mytest.c -o mytest -I ./lib-static/include/ -L ./lib-static/lib/ -lmymath 
  • -L选项后带的是库的路径。
  • -I选择后带的是头文件的搜索路径。
  • -l选项带的是库的名字库名要去掉前面的lib和后缀.a

如果将静态库安装到系统的库目录下一般是/lib64/就不用上述那么麻烦了 编译的时候只需要用-l指定库名即可~

gcc mytest.c -lmymath

静态库的特点

静态库的特点是库中的实现已经被编译链接进入了可执行程序即便我们将库给删除也不影响可执行程序的运行。

在这里插入图片描述

3.2 动态库的使用

动态库和静态库的链接方法基本是一样既可以将动态库安装到系统目录下也可以如下方式指定去找

gcc mytest.c -o mytest -I ./lib-static/include/ -L ./lib-dyl/lib/ -lmymath

用法一样而且选项的含义也是一样的。

不一样的是运行起来之后

在这里插入图片描述
直接运行发现报错了报错的内容是这个错误意味着你的程序无法找到名为 “libmymath.so” 的共享库文件。

使用ldd命令查看mytest可执行文件的动态库结构会发现我们写的动态库是没有找到的

在这里插入图片描述
补充

ldd 是 Linux 系统下的一个命令用于查看可执行文件或共享库文件所依赖的动态链接库Dynamic Link Library。它会列出指定文件所需要的动态链接库及其路径。这个命令在解决程序依赖性问题、查找缺失的库文件或调试系统问题时非常有用。

原因解释

  • -I -L -l是编译器的选项这些查找属性本身是告诉gcc在哪里找头文件在哪里找对应的lib找的是什么lib。这些告诉的是gcc并且形成了可执行程序。
  • 所以头文件和库的位置只告诉了gcc程序一旦执行起来可执行程序就成了进程了但是没有人告诉进程这个动态库在哪里。
  • 程序启动的时候找不到库因为不在系统库目录里系统找不到。

3.3 程序运行时找到动态库

3.3.1 导入到系统路径下

  • 当程序在运行时需要加载第三方动态库时操作系统会在默认的库搜索路径中查找该库。
  • 如果库文件没有在默认的搜索路径中就会导致找不到库的情况。
  • 将动态库拷贝到系统库目录下一般都是安装到/lib64下不推荐因为会污染系统库。

3.3.2 环境变量LD_LIBRARY_PATH

通过修改环境变量的方式来实现增加动态库的查找路径

在这里插入图片描述

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/Zh_Ser/linux/lesson22/test/lib-dyl/lib

在这里插入图片描述
此时环境变量就导入成功啦。

我们再ldd和运行一下看看

在这里插入图片描述

ldd命令的结果也显示出了我们自己写的动态库的路径。

但是依旧有个缺点在Shell重启之后导入的环境变量就不见了还要重新导入。

3.3.3 修改系统配置文件

通过配置文件来向系统表明要向哪里搜索。

除了修改环境变量我们还可以修改/etc/ld.so.conf.d下的文件

在这里插入图片描述

这个路径下表明的是系统里面如果自定义了动态库那么此时系统在扫描系统的路径时除了在系统对应的库路径扫描对应的库之外还去一个一个的读取这里面的配置文件根据配置文件里面的内容来找到对应的动态库。

这里的操作非常简单我们只需要在该目录下新增一个.conf文件并在里面写入动态库的绝对路径即可~

sudo touch /etc/ld.so.conf.d/my.conf
sudo vim /etc/ld.so.conf.d/my.conf
  • 进入插入模式把自己自定义的动态库的路径粘贴进去。
  • 我们刚刚写的配置文件里保存的是自己写的动态库的路径。
  • 然后ldconfig指令让这个文件生效。sudo一下
sudo ldconfig #子用户权限不够需要加sudo
  • 执行完之后就会默认的在我们系统的内存当中帮我们把刚刚自己新定义的conf加载到内存里。
  • ldd mytest我们就能看到libmymath.so =>我们自己写的库的路径
  • 此时再运行就可以跑了甚至将Shell关掉重启之后还是能跑。

3.3.4 创建软连接

/lib64下创建一个软连接来帮我们查找动态库。

sudo ln -s /home/Zh_Ser/linux/lesson22/test/lib-dyl/lib/libmymath.so /lib64/libmymath.so
  • 在Linux系统中链接动态库时默认情况下会搜索一些默认的路径来查找所需的动态库文件。
  • 其中一种常见的默认路径是 /lib 和 /lib64。
  • 而我们则是在/lib64/下创建一个我们自己写的动态库的快捷方式libmymath.so

4. 动静态库对比

动静态库优缺点对比看之前我的博客 【动静态库复习 - 传送门】

接下来我们要好好讲讲为什么动态库在程序运行时要找库而静态库则不需要。

4.1 为什么运行时找动态库

刚刚我们讲了当可执行程序运行起来时找不到动态库的四个解决办法但是具体为什么会运行时找不到我们来讲讲原因。

程序和动态库是分开加载的

  • 当你运行一个可执行程序时操作系统会加载可执行程序本身并为其分配内存空间。然后动态链接器会在需要的时候加载所需的动态库。
  • 动态库是在运行时动态加载的也就是说在程序执行过程中如果有代码调用了动态库中的函数或使用了其中定义的变量动态链接器才会将相应的动态库加载到进程的内存空间中。动态链接器会解析可执行程序中的动态库依赖关系并按照依赖关系来加载动态库。这样可以实现动态共享代码和资源的重复利用减小可执行程序的大小。
  • 动态库的加载是延迟的即只有在需要时才加载。如果某个动态库在程序运行的过程中没有被使用到那么它也不会被加载到内存中。
  • 总结起来可执行程序在运行时首先加载然后动态库在需要时被加载以满足程序对其中定义的函数和变量的调用和访问需求。这种分离加载的机制提供了更高的灵活性和代码复用性。

程序在运行期间可能找不到库的原因是

  • 程序和库是分开加载的运行程序程序形成进程将程序代码加载到内存里。
  • 同时也要将自己写的库加载到内存里如果库不存在的话还要映射到进程上下文当中。

在这里插入图片描述
将库文件加载到内存里前提条件是:

  • 进程运行的时候如果要动态加载它需要的库。
  • 前提条件是先找到这个库在哪里。
  • 要建立地址空间和库的映射关系。
  • 我们生成可执行程序有的代码是需要跳转到库中执行的
  • 用多少加载多少必须将库加载到内存中去。

所以这个库如果要被加载就必须要被找到 所以就倒逼着进程在运行时必须在运行的时候找对应的库找不到就报错进程直接终止。

所以在我们代码区域对库进行调用时也是在进程地址空间当中进行来回跳转。

动态库shared libraries因为可以在运行期间被多个进程所共享所以叫做共享库。

4.2 静态库不用找的原因

如果是静态链接每个进程都有一份代码就是将库代码编进了自己的代码当中。

  • 在编译可执行程序时静态库的代码和数据会被完整地复制到可执行程序中。
  • 当使用静态库时编译器将库中的目标文件已经经过编译的代码直接嵌入到最终的可执行程序中。
  • 这意味着在编译可执行程序时静态库的代码和数据就已经被加载到可执行程序中并成为可执行程序的一部分。
  • 由于静态库在编译时被静态链接到可执行程序中所以在运行时并不需要额外加载动态库。可执行程序已经包含了静态库的完整副本所有所需的函数和符号定义都可以在可执行程序内部找到。

优点

  • 这种静态链接的方式使得可执行程序更加独立不需要依赖外部的动态库文件。
  • 同时静态库的使用也简单直接不需要考虑动态库的查找和加载过程。

缺点

  • 然而静态库的缺点是占用了更多的磁盘空间并且无法进行更新或替换。
  • 每个使用该静态库的可执行程序都会有一份完整的库的副本这可能导致可执行程序的大小增加和资源重复浪费。

动态库中形成的目标文件是加了-fPIC选项

  • -fPIC是一个编译选项用于告诉编译器生成位置无关代码Position Independent Code。
  • 这对于生成共享库shared library或动态链接库dynamic link library非常有用因为这些库在不同的内存地址空间中加载并且需要解析和重定位符号表。
  • -fPIC选项的作用是将编译生成的目标文件中的代码和数据访问都使用相对地址而不是绝对地址
  • 这样做的好处是当共享库被加载到内存中并进行地址映射时不需要修改库中的代码和数据而只需要调整引用该库的程序中的全局偏移量即可。
  • 这使得共享库能够以更加灵活和独立的方式被多个程序共享使用。

5. 总结

  • 无论动静态库在使用的时候用gcc生成可执行程序时都要指明库所在的路径。
  • 而在运行时则是静态库不需要再去找库的位置而动态库则需要再找库的位置。
  • 静态库是在编译时加载到可执行程序中的当可执行程序启动时静态库的代码已经完全集成在程序中不需要额外的加载过程。
  • 而动态库则是运行时才加载到可执行程序中的当可执行程序启动时操作系统会根据程序中引用的动态库的名称和路径来查找并加载这些动态库。
  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
    标签: linux