hello~ 很高兴见到大家! 这次带来的是Linux系统中关于库制作与原理这部分的一些知识点,如果对你有所帮助的话,可否留下你宝贵的三连呢?
个 人 主 页: 默|笙

在这里插入图片描述



一、什么是库?

1.1 动态库和静态库

  1. 在我们编写c语言/c++代码的时候,会包含头文件来使用printf这些函数,但是头文件只是声明,这些函数的定义由谁提供?由库来提供。

在这里插入图片描述

  1. ldd 命令能精准查出一个可执行文件依赖了哪些动态链接库。比如我用 C 语言写了一个最简单的 helloworld 程序,编译生成 test 可执行文件后,执行 ldd test 就能清楚看到:这个 test 程序依赖了 C 语言标准库—— 这也是为什么我们写 C 程序能直接用 printf 等函数的原因。
  2. 如何快速识别库名:去掉前缀 lib 和后缀(版本号 / 类型后缀),剩下的就是库的名字。比如 C 语言标准库的完整名字是 libc.so.6:前缀:lib(Linux 库的通用前缀),核心名:c(代表 C 标准库),后缀:so.6(so 是 Linux 动态库后缀,6 是版本号)。
  3. 库分为两类,一类是静态库,另一类是动态库。windows系统和Linux系统下它们的后缀名不同。

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

  1. 库如果是动态链接的就是动态库,如果是静态链接的就是静态库。动态链接和静态链接有什么不同?打个比方:有一个网吧,里面有很多电脑,我们通过某种途径知道了去这个网吧的路线 —— 这就对应动态链接:我没有把 “电脑(库)” 装到自己这,只是知道它在哪,要用的时候直接去网吧(系统里找对应的动态库)用就行;而如果网吧倒闭了,我只能自己攒钱在家里买一台电脑 —— 这就对应静态链接:我把 “电脑(库)” 真正装到了自己家里(编译时把库的代码完整打包进可执行文件),不用再找网吧,自己随时都能上网(程序运行不依赖外部库)。

动态链接和静态链接各有优劣:

  1. 所占空间:动态链接没有把库的代码完整打包进可执行文件,只是存了个库的 “地址”,所以可执行文件占的空间很小;而静态链接是把库的代码全打包进去了,文件体积就会大很多。
  2. 维护升级难度:动态链接如果库更新了,不用改程序里的 “地址”,程序运行时直接用新的库就行;但静态链接是把旧版库的代码打包进程序了,想用上新版库,必须重新编译打包新的可执行文件。
  3. 运行风险:动态链接的程序依赖系统里的库文件,如果这个库丢了、路径变了,程序运行就会报错;而静态链接的程序不依赖外部库,就没这个问题。

在这里插入图片描述

  1. c/c++使用gcc、g++编译链接的时候,默认使用的是动态链接。如果要使用静态链接,要在用使用gcc、g++编译链接命令行后面加上-static。当然首先得安装静态库。

CentOS7:sudo yum install -y glibc-static

在这里插入图片描述

1.2 库的本质

在这里插入图片描述

  1. 我们把 C/C++ 源文件处理成可执行文件时,原本会经过四个核心步骤:预处理、编译、汇编、链接。而在日常表述中,我们常把前三步统称为 “编译”—— 这样一来,整个文件处理过程就可以简化成两步:先编译,后链接。
  2. 经过编译(前三步)后,每个 .c 源文件都会被单独处理,生成对应的 .o 目标文件。关键要记住:生成单个 .o 文件的过程只和当前这个 .c 文件有关,和其他源文件完全无关 —— 比如编译 test1.c 生成 test1.o 时,编译器不会读取、也不会依赖 test2.c 的任何内容。
  3. 链接的时候,就是将编译生成各个独立的.o文件整合到一起,这个时候才真正依赖其他的.o文件,库文件也是在这个时候进行关联的。
  4. 所以,在有了源文件编译形成的.o文件之后,我们就不需要原来的.c文件了,只需要知道这些文件里面函数的使用方法也就是.h文件,就能够使用.c文件里面所定义的函数了。这也是库文件的核心使用逻辑:使用库函数时,我们不用去编译库函数的源文件(甚至根本看不到库的 .c 源码),只需要在自己的源文件里包含对应的头文件(比如 #include <stdio.h>),然后在编译链接阶段把库和自己的代码关联起来,就能正常使用库函数了。
  5. 库是.o文件的一个集合,它把大量实现不同功能的 .o 文件打包整合在一起(C 标准库就包含了实现 printf、scanf、malloc 等函数的各种 .o 文件)。这样我们不用逐个管理零散的 .o 文件,只需要链接整个库,就能调用库内所有相关函数,大大简化了开发流程。
    6.库函数可以帮助用户完成内存 / 资源的申请,比如fopen函数的源代码实现封装在标准库中,调用时由库函数内部执行具体的内存 / 资源申请操作;但发起申请的主体是用户程序(通过调用库函数触发),最终申请到的资源(如文件指针)的归属和管理责任也归用户(比如需要用户调用fclose释放)。

1.3 制作库

制作静态库

  1. 制作静态库需要用到命令ar,它是Linux系统下创建、修改、查看静态库(.a文件)的核心命令。全称是 archiver(归档工具)—— 静态库本质是多个.o 文件的 “归档包”,ar 就是用来打包 / 管理这个 “包” 的工具。

ar -rc libXXX.a *.o

  1. 选项-r的作用是:将.o文件添加/替换到静态库中(库不存在则创建,库存在则替换库里面所有的.o文件)。选项-c的作用是:静默创建静态库,也就是创建时不输出冗余提示。

在这里插入图片描述

使用静态库

  1. 我们先创造一批.o文件,再用ar将这些.o文件打包成一个静态库libmystdio.a。至于如何使用我们自己创造的这个静态库,就离不开gcc的 -l 和 -L 选项了。-l 选项是告诉编译器我们的库叫什么名字,选项 -L 是告诉编译器我们的库在哪里,后面跟绝对路径或相对路径

在这里插入图片描述

  1. 生成了库文件之后,再将main.c生成对应的.o文件,最后将main.o文件与库文件进行链接形成目标可执行文件target。
  2. 为什么使用c语言标准库的时候不用告诉gcc库的名称?这是因为gcc它专门编译c语言,它当然认识c语言标准库。为什么也不用告诉编译器库在什么位置?gcc它会去/lib64这个路径下面去找。同样,如果我们将我们的静态库拷贝到这个路径下面,那么我们也不用告诉gcc库在什么地方了,但名称还是得通过 -l 指定。

在这里插入图片描述

  1. 我们给别人提供一个库(无论静态库 .a 还是动态库 .so)时,需要提供两类核心文件:一是库文件本身,里面包含函数的二进制实现 / 定义,二是配套的头文件 —— 头文件里要写清楚所有对外函数的声明,它就是我们库的使用说明。因为库文件本质是 .o 二进制文件的集合,使用者无法直接从二进制代码中看懂函数的使用方式,必须靠头文件明确函数的调用规则,才能正确使用库中的函数。一般就是像上图中将它们打包成一个目录。
  2. 其他人应该如何使用我们提供的这个库呢?
  1. 安装到系统目录下,也就是拷贝到系统目录/lib64路径下,也可以使用软链接,这样就不用拷贝到系统目录下。库文件和头文件头进行拷贝或软链接。

库文件软链接
在这里插入图片描述
在这里插入图片描述

  1. 使用 -l(小L)-L -I(大i)这三个选项。大写i是告诉编译器头文件(.h)的存放路径(仅当头文件不在 /usr/include 等系统默认路径时需要)。

在这里插入图片描述

制作动态库

在这里插入图片描述

  1. 动态库的打包和使用无需 ar 命令(ar 仅用于静态库打包),而是全程使用 gcc/g++ 完成,这也体现了动态库在实际开发中更常见。打包动态库有两个核心要求:① 编译生成 .o 文件时必须加 -fPIC 选项(生成位置无关代码);② 链接生成动态库时必须加 -shared 选项(指定生成共享库而非可执行文件)。

使用动态库

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

  1. 动态库与静态库的核心差异之一是运行依赖:通过静态库编译生成的可执行文件可直接独立运行,原因是链接阶段会将静态库中用到的所有代码完整拷贝并整合到可执行文件中,最终可执行文件不依赖任何外部库文件;而动态库仅在编译链接时告诉编译器 “库文件的位置”,链接阶段并不会拷贝动态库的代码到可执行文件中(仅记录动态库的依赖信息),因此操作系统(OS)运行该可执行文件时,还需要独立查找并加载对应的动态库文件

动态库库运行时的查找问题

  1. 由于文件运行时OS还得去找动态库的位置,共有四种方法可以解决问题,这里推荐前三种。
  1. 拷贝 .so 文件到系统共享库路径下,一般指 /usr/lib、/usr/local/lib、/lib64 或者开篇指明的库路径等。

  2. 向系统共享库路径下建立同名软连接

  3. 更改环境变量: LD_LIBRARY_PATH。
    在这里插入图片描述

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

动态库与静态库优先级问题

  1. 如果一个系统里面既有静态库,又有动态库,会优先用哪个库?动态库
  2. 如果要使用静态库,链接时要带上-static选项,同时这个选项也要求每个依赖库都提供静态版本,并全部静态链接到我们的可执行程序。如果带上了-static选项但是只有动态库,没有静态库,那么程序会报错。
  3. 如果只提供动态库,那就只能对这个库进行动态链接;只提供静态库,就只能对这个库进行静态链接。要更新 / 修改动态库,只需替换库文件本身,不需要重新编译链接源程序;而静态库一旦更新 / 修改,就必须将源程序重新进行一遍编译链接,才能生效
  4. 如果只提供静态库,链接时即使采用默认的动态链接方式(不额外加静态链接参数),系统因找不到对应的动态库,会自动选择静态链接这个库。

今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~
让我们共同努力, 一起走下去!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐