Linux 动静态库

目录

库的概念

动静态库的制作

动态链接与静态链接

库的使用


库的概念

程序库(Library),一般简称为“库”,是一组预先编写好的可重用代码的集合。这些代码可以包括函数、类、变量、常量、数据结构、宏等,它们通常被打包成一个单独的文件,以便在需要时被其他程序调用和使用。库的主要目的是促进代码重用、提高开发效率和维护性。通过将常用的功能封装到库中,开发者可以在不重复编写代码的情况下,直接引用库中的功能来实现自己的应用程序。这样可以减少开发时间、减少错误和提高代码的可维护性。

简单来说,库就是对多个可重定位目标文件(.o文件)合并后的文件。只不过库的制作比较复杂,并不是只是简单的文件合并。

其中库分为静态库和动态库两种:

  1. 静态库(static library):在编译链接阶段,静态库的代码会被复制到最终的可执行文件中。因此,可执行文件独立于库文件,但会增加最终程序的体积。

  2. 动态库(dynamic library):在运行时,动态库的代码被加载到内存中,并由多个应用程序共享。这样可以节省内存空间,并且允许在不重新编译应用程序的情况下更新库文件,但这样也会增加动态库设计的复杂程度。

在Linux的平台下,静态库文件的后缀是 .a,动态库的后缀是 .so 。
在window的平台下,静态库文件的后缀是 .lib,动态库的后缀是 .dll。

动静态库的制作

静态库

在Linux中,通常使用ar命令制作静态库。ar是Linux下的一个归档工具,类似于tar,但区别在于tar是将多个文件打包成一个压缩包,而ar是将多个文件的内容合并到一个文件中。

tar用于对目录结构做归档,这样更适合互联网上源码包的分发,像tarball。ar更适合将分散的文件归档成assembled object code,所以在编译过程中连接器ld使用了ar。

  摘自:ar和tar有啥区别 - 博客园 (cnblogs.com)

通过ar命令的制作静态库的方法是,将多个可重定位目标文件(即.o文件)归档为一个静态库,代码示例如下:

ar -rc libxxx.a f1.o f2.o ……

libxxx.a是静态库的名称,后面的f1.o f2.o ……表示的是对应的可重定位目标文件。其中参数 -r 是如果存在同名文件就替换(replace)的意思,-c选项的建立对应的存储文件(create)的意思。

至于ar指令的其它参数可以参考:Linux ar命令 | 菜鸟教程 (runoob.com)

此外,libxxx是库文件的一种格式,libxxx.a表示是静态库,libxxx.so表示是动态库。


动态库

动态库又叫共享库。单从指令来讲,动态库的制作要比静态库简单一些,用gcc或g++就可以实现,用法示例如下:

gcc -fPIC -o libxx.so src1 src2 …… -shared

其中,libxxx.so是生成的动态库,src是用到的源文件,可以是.c文件,也可以是.o文件等。

而 -shared 选项表示的是产生一个共享对象,这里就是共享库的意思。

-fPIC 选项表示的是产生位置无关码,至于什么是位置无关码,请参考下面的内容:

-fPIC 作用于编译阶段,告诉编译器产生位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而动态库可以被在内存的任意位置,都可以正确的执行。

在程序运行时,动态链接库被加载到内存中。如果该库被编译时没有使用 -fPIC 选项,那么它的代码段中的指令和数据引用会是绝对地址。当加载器将库加载到内存中的时候,这些绝对地址就需要进行修正,使得它们能正确地映射到进程的地址空间中的正确位置。这个过程就是重定位。但是这个过程会修改程序的代码段,即重定位会修改代码段。

避免重定位的优势在于,不需要在每个使用该库的进程中都进行重定位操作。相反,这个库在内存中只需要一份副本,多个进程可以共享这份副本,节省了内存空间,并且避免了重定位带来的运行时开销。

需要注意的是,gcc有一个-static选项表示的是限制只使用静态库,禁用动态库,所以一般-static编译出来的可执行程序要比动态链接的大很多。

动态链接与静态链接

动态链接

至此,我们知道生成动态库时需要设置地址无关码,那么程序在链接动态库函数时,就是通过 “动态库的起始地址 + 库函数偏移量”的方式进行链接访问的。

Linux 动静态库_第1张图片

我们知道进程的进程地址空间在堆和栈之间是还有一个共享区的,这个共享区中存放的并不是直接存放的共享库的内容,而是存放的共享库中函数或变量在内存中的映射,如下图所示。

Linux 动静态库_第2张图片 原图出处: Linux基础IO【软硬链接与动静态库】CSDN博客

所以,动态链接就是通过在内存中对这些动态库统一管理起来,并通过页表将共享区中的函数与物理内存之间建立映射。即动态库被加载在内存中,可以供多个使用库的程序共享映射到自己的虚拟地址空间使用,因此可以减少页面交换以及降低内存中代码冗余,并且因为与源程序模块分离,因此开发模式比较好。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。

如果我们在进程启动时就将所有关联的动态库与程序之间进行绑定,那么其实是很影响效率的,所以通常操作系统会采用一种叫做延迟绑定的方式来解决这个问题。延迟绑定是指在程序执行过程中,直到某个函数或变量第一次被调用或访问时才将其与相应的库中的实现进行绑定的机制,这个机制有助于提高程序的启动速度。不过一旦某个函数或变量在共享区存在映射之后,那么位置也就固定了。如果想要详细了解延迟绑定,可以参考:延迟绑定-CSDN博客


静态链接

与动态链接不同,静态链接相对要简单粗暴一些。静态链接是将静态库直接“嵌入”在可执行程序中的,所以静态链接的可执行程序通常要比动态链接的相对较大。


动静态链接对比

静态库的特点:

  • 静态库对函数库的链接是在编译期完成的。
  • 程序在运行时与函数库再无瓜葛,移植方便。
  • 静态链接是将静态库直接“嵌入”到可执行程序中的,所以静态链接相对动态链接比较浪费空间资源。
  • 当静态库更新时,整个可执行程序所包含的内容都需要重新编译,维护成本较高

动态库的特点:

  • 动态库将一些库函数的链接载入推迟到程序运行的时期。 
  • 程序的运行需要依赖动态库,没有对应的动态库就无法运行。
  • 动态库在程序运行时才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,即增量更新。
  • 当动态库更新时,只需要将对应的动态库重新编译即可,不需要将所有内容都重新编译一遍。

库的使用

三个选项

在Linux中,如果想要使用自定义的库文件,就需要在使用gcc时额外指定三个参数:

  • -l 选项 - 指定链接的库文件

-llibrary

-l library
        链接器会在标准搜索目录中和 -L 指定的搜索路径中,寻找名为liblibrary.a或liblibrary.so的库文件。而且 -l 与library之间加不加空格都可以。

  • -L 选项 - 指定库文件的搜索路径

-Ldir  

-L dir

        在 -l 选项的搜索路径列表中添加 dir 目录。其中 -L 与 dir 之间加不加空格都可以。

  • -I 选项 - 指定头文件的搜索路径

-Idir  

-I dir  

        在头文件的搜索路径列表中添加dir目录。其中 -I 与 dir 之间加不加空格都可以。

因此,我们在使用自定义库文件时,就可以像下面这种:

gcc -o myproc fun.c -I mylib/include/ -l mylib -L mylib/lib/

三个目录

而在Linux中,系统标准目录中的库文件(包含动态库和静态库)是放在对应的lib目录下的,即规定32位的可执行程序用到的库文件放在 /usr/lib/ 目录下,64位的可执行程序用到的库放在 /usr/lib64/ 目录下,其中根目录下的 /lib 和 /lib64 分别是 /usr/lib/ 和 /usr/lib64/ 的软链接。此外,系统中的头文件是统一放到 /usr/include/ 目录下的。

也就是说,操作系统在用到对应的库文件时,就会到指定的lib目录下去找,32位程序到 /usr/lib 目录下找,64位程序到 /usr/lib64 目录下找,头文件到 /usr/include/ 目录下找。如果在编译链接过程中,在对应的目录下没有找到所用到的头文件或库文件,那么此时就会异常出错。

所以,很多时候我们可以把一些常用的动静态库或者头文件放到对应的系统目录下,这样在使用时系统就可以检索的到了。实际上,某些库文件的安装其实就是这个原理。

当我们将自定义的库文件及头文件添加到对应的系统目录下之后,我们在编译链接可执行程序时,指令就相对就比较简洁了,只需要使用 -l 选项指定对应的库文件即可。

gcc -o myproc fun.c -lmylib

不过要想对 /usr/ 目录下的内容进行修改,是需要root权限的,这点需要注意。


加载动态库

需要注意的是,动态库链接的可执行程序在运行时需要额外加载动态库。

**方法列举如下:**

1. 将动态库所在路径添加到 LD_LIBRARY_PATH 环境变量中

2. 将路径内容写入 /etc/ld.so.conf.d/ 目录下的文件中,通常一个文件中的不同路径之间要换行

3. 将库或库的软链接放入 /lib64或/usr/lib64(64位可执行程序,32位放入/lib或者/usr/lib下),同时需要将库中用到的头文件放入 /usr/include 中

至于将库或库链接放到与可执行程序同一目录下的这种方法,centos7试了可以,centos8不行,所以这种方法不具备统一性,最好不要使用

你可能感兴趣的:(linux)