【基础IO】谈谈动静态库(怒肝7000字)

2024-04-20 23:36
文章标签 基础 静态 io 7000 谈谈 怒肝

本文主要是介绍【基础IO】谈谈动静态库(怒肝7000字),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 实验代码样例
  • 静态库
    • 生成一个静态库
    • 归档工具ar
    • 静态库的链接
  • 动态库
    • 创建动态库
    • 加载动态库
  • 动静态链接
    • 静态链接
    • 动态链接
    • 动静态链接的优缺点

前言

在软件开发中,库(Library)是一种方式,可以将代码打包成可重用的格式,供其他程序调用。库可以分为静态库Static
Libraries)和动态库Dynamic Libraries 或 Shared Libraries)。这两种类型的库在链接和执行时有各自的特点和用途。本篇文章将围绕动静态库的原理及其使用展开讲解。

实验代码样例

/add.h/#ifndef __ADD_H__#define __ADD_H__ int add(int a, int b); #endif // __ADD_H__/add.c/#include "add.h"int add(int a, int b){return a + b;}/sub.h/#ifndef __SUB_H__#define __SUB_H__ int sub(int a, int b); #endif // __SUB_H__/sub.c/#include "add.h"int sub(int a, int b){return a - b;}///main.c#include <stdio.h>#include "add.h"#include "sub.h"int main( void ){int a = 10;int b = 20;printf("add(%d, %d)=%d\n", a, b, add(a, b));a = 100;b = 20;printf("sub(%d,%d)=%d\n", a, b, sub(a, b));}

解释以上代码:
该代码分为三个部分,一个是main函数,一个是add函数的声明与定义文件,一个是sub函数的声明与定义文件。

静态库

静态库是一种在程序编译时就被整合到可执行文件中的代码和数据集合。一般来说,无论是动态库还是静态库,其库中的内容都是一些被编译过但是还未被链接目标文件(以.o或者.obj结尾的二进制文件)。

为什么要将这些目标文件打包成库呢?

  1. 首先就是便于代码复用。将常用的功能打包成库使得这些代码可以轻松的在多个项目中重用。这大大节省了开发的时间,也有助于提高代码的一致性
  2. 使项目设计变得更加模块化。需要什么功能就添加什么库,将整个项目分解为更小更容易管理的模块。每个模块的库执行特定的功能,通过提供接口与其它模块交互。这有助于提高代码的维护性与可读性
  3. 版本控制和兼容性。库可以独立于使用它们的应用程序进行版本控制。开发者可以对库进行更新和改进。对于我们使用者来说,只需要更新一下就能使用最新的库了。
  4. 团队协作库允许不同的开发者或小组专注于特定的功能领域。例如,一个团队可以负责数据库交互的库,而另一个团队则可以专注于用户界面的组件。这样的分工促进了专业化,可以提高开发效率和产品质量。

生成一个静态库

根据库的特点,我们需要先将add.c文件和sub.c文件编译成目标文件,使用带-c选项的gcc指令:
在这里插入图片描述
接下来使用归档工具 ar 命令将一组对象文件打包成一个库文件。具体使用方式如下:
ar -rc libmymath.a add.o sub.o
其中,-rc是参数,表示替换、创建。libmymath.a表示要生成的库文件(一般静态库是以.a为后缀)。add.osub.o则是输入的对象文件。
在这里插入图片描述
用指令ar -tv查看库中的目录列表
在这里插入图片描述
选项t表示列出静态库中的文件,v表示详细信息。

归档工具ar

ar是一个用于创建和管理归档文件的工具,通常用于创建静态库。生成的归档文件是一个单独的文件,用来存储多个其它的文件和目录,常常在编译链接阶段用于组织静态库中的对象文件(.o文件)。ar来源于archive(归档),其主要功能就是把多个文件合并成一个文件,以便于管理。

虽然我们可以用-tv选项查看归档文件中的目录,但它本身并不是一个目录,只是看起来像而已!此外,ar的功能其实与zip类似,但是ar对于处理这种目标文件是非常有效的。

给出ar指令的选项功能:

  • r: 插入文件到归档中(如果归档已存在,这个选项会替换或添加文件)。
  • c: 创建归档文件,如果它不存在。
  • s: 创建一个对象文件索引(符号表),这对于链接器加速访问归档中的目标文件很重要。
  • t: 列出归档内容。
  • x: 从归档中提取文件。
  • d: 从归档中删除文件。
  • u: 只有当被添加的文件比归档中现有的同名文件更新时才添加文件。

总之,区别于我们用gcc一个一个链接目标文件,这种打包成库的方式简化了链接和构建的过程,显得非常的方便且灵活。

静态库的链接

当创建可执行文件时,如果程序依赖于某个静态库,链接器(linker)会将静态库中的相关对象文件整合到最终的可执行文件中。回到上面的代码样例。我们的main.c文件依赖于add与sub函数的实现,如果不链接库。按照我们之前的方式,只能一个一个链接源文件:
在这里插入图片描述
而现在我们已经将add.o与sub.o打包成了静态库libmymath.a,该怎么使用呢?
考虑使用以下指令:
gcc main.o -L/path/to/library -lexample -o main

  • 其中 -L/path/to/library 告诉链接器在哪个目录下查找库文件。如果库文件在标准库路径下例如 /usr/lib/ 或 /usr/local/lib/,可以省略这个选项。
  • -lexample 指定链接器使用名为 libexample.a 的库。注意这里的使用命名规则,并不是直接将库静态库的全名加上去,而是要进行一些“处理”。因为链接器会自动寻找以lib开头,.a结尾的文件。我们只需要提供去掉lib和.a的部分。-l选项表示指定添加库。

库搜索路径:

  • 从左到右搜索-L指定的路径
  • 由环境变量指定的目录 (LIBRARY_PATH)
  • 标准库路径 例如 /usr/lib/ 或 /usr/local/lib/

所以针对样例代码,我们可以这样链接静态库:
在这里插入图片描述
这样我们就成功的链接了一个静态库。
值得注意的是,一旦我们成功链接了某个静态库之后,该静态库中的所有数据和代码就存在可执行文件中了。所以我们之后即使把静态库删除也不会影响到程序的执行。这是一个一次性的过程。

动态库

动态库(Dynamic Libraries)是现代软件开发中常用的一种资源共享模块化技术。动态库能够使多个程序共享同一份库代码,而不需要将这些代码复制到每个程序的可执行文件中,从而节省系统资源并便于维护和更新。

与静态库不同的是,动态库被链接后存在于进程的共享区域内,该区域内的数据和代码可以供多个进程使用。而且是用时访问,即程序在运行阶段会不断地访问。也就意味着,如果我们在生成可执行文件之后,将动态库删除,程序便会报链接错误。这是和使用静态库不一样的地方。

我们可以观察动态库在程序的内存分布:
在这里插入图片描述
其中动态库在内存的数据共享区,该区域于其它进程共享数据。而静态库中的数据被链接之后就成为了代码段与数据段的一部分。

创建动态库

创建静态库使用了归档工具ar,而创建一个动态库gcc工具就可以。
考虑以下指令:
gcc -fPIC -c sub.c add.c
gcc -shared -o libmymath.so sub.o add.o

首先解析第一条指令:
选项-fPIC表示生成位置无关码。
什么叫位置无关码?

位置无关码的意思就是,生成的代码在内存中可以被加载到任何位置,而不是某个固定的地址。这种特性对于动态库尤其重要,因为动态库需要能够被多个不同的程序共享,并且每个程序可能将库加载到不同的地址空间。

位置无关码的寻址方式为相对寻址,可以将代码中的所有指针认为是一个个偏移量,不同的程序给与它不同的初始地址,这样就能灵活的将库加载到其它的地方。

生成位置无关码是创建动态库的标准做法,因为它确保库能在不同的应用程序和不同的运行实例中被正确地共享和使用。如果我们不用-fPIC选项生成位置无关码,程序在运行时就会报错。

在这里插入图片描述

所以第一条指令的意思就是将sub.c和add.c文件分别经过编译生成目标文件。
接下来就是将这些目标文件打包成库。

分析第二条指令:
选项-shared表示 链接器 生成一个动态库而不是默认的可执行文件。生成的动态库文件一般以.so结尾。

于是呢,经过这两条指令我们就得到了一个libmymath.so动态库.
在这里插入图片描述

加载动态库

当我们已经通过gcc编译器得到了一个动态库,该如何使用这个动态库呢?
如果我们直接像使用静态库那样直接链接,确实能编译通过,但是一旦我们尝试运行,就会得到以下报错:
在这里插入图片描述
为什么报错显示找不到这个动态库呢?我们不是在链接动态库的时候告诉了编译器动态库的路径了吗?

这一点非常容易理解。

我们确实将动态库路径告诉了gcc编译器,也确实编译成功了,得到了一个可执行文件。但是一个动态库是需要在运行时被访问的编译器能找到这个动态库并不表示操作系统能找到这个动态库

那为啥静态库这样运行就没问题呢?这是因为静态库是一次性工程,在链接阶段就把所有的代码和数据拷贝到程序的内部了!往后这个静态库文件在哪已经无所谓了。

所以,对于一个动态库而已,链接一个动态库的时候要告诉编译器库在哪,运行的时候就要告诉操作系统在哪。

解决方法:

运行时,操作系统会默认在/lib(不同的操作系统名字可能不一样)这个目录下去找动态库
在这里插入图片描述

1.将我们自己的动态库拷贝到/lib里面,就能成功运行了:
在这里插入图片描述
在这里插入图片描述

2.在/lib目录下创建一个动态库的软链接:
在这里插入图片描述
这样操作系统也能通过软链接找到我们的动态库
3.修改动态库默认路径的环境变量LD_LIBRARY_PATH:
考虑以下指令:
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
其中/usr/local/lib表示库路径
在这里插入图片描述
但是这样设置的环境变量在下一次登录就失效了,要想永久生效就得修改配置文件.bashrc:
在这里插入图片描述

找到LD_LIBRARY_PATH配置项,并在其路径下添加库的路径,这样每次登录都会自动生效。
如果你发现你的./bashrc没有LD_LIBRARY_PATH那你可以自己手动加一个环境变量LD_LIBRARY_PATH过去:export LD_LIBRARY_PATH=/path/to/your/libs:$LD_LIBRARY_PATH

动静态链接

静态链接

静态链接是在程序编译时将所有需要的库文件(通常是 .a 或 .lib 文件)内容直接复制到最终的可执行文件中的过程。这样,程序在运行时不再需要任何外部库。注意与静态库的区别,链接是一个动作,而库是一个名称。我们常把链接静态库的过程称为静态链接。

值得注意的是,gcc默认是动态链接。我们可以用指令file观察到这一点。
在这里插入图片描述
那如何使gcc静态链接目标文件呢?
使用-static选项:gcc -o main main.c -L. -lmth -static
在这里插入图片描述
再使用file观察,发现提示该可执行文件不是一个动态链接文件
在这里插入图片描述
这里可能会有人有疑问,为什么之前我们用的是静态库,默认是动态链接,还显示是动态链接呢?
如果不显示使用-static选项,系统会只将声明的这个库进行静态链接,其它的库就还是动态链接。
比如我们之前的gcc -o main main.c -L. -lmth,假如这个mth.a是一个静态库,那么就只会静态链接这个库,其它库都是动态链接。

动态链接

动态链接是编译过程中,程序被构建为在运行时加载外部共享库(如 .so 或 .dll 文件)的过程。这意味着程序在运行时依赖于这些库文件。动态链接一般用来链接动态库。
gcc默认就是动态链接,因此无需其它选项。值得注意的是,如果我们声明的库路径下面包含静态库和动态库,即同名的库,只是后缀不一样。gcc还是会优先考虑链接动态

动静态链接的优缺点

优点:

  • 静态链接生成的可执行文件包含了所有必要的代码,不依赖于外部的库文件,这使得部署更简单,只需要分发单一的可执行文件。
  • 静态链接性能快。在某些情况下,静态链接的程序启动速度比动态链接的程序快,因为它们在启动时不需要加载外部库。
  • 动态链接节省空间:多个程序可以共享同一个库的单一物理副本,这样可以显著减少系统的总体占用空间。
  • 动态链接更新方便,不需要重新编译,只需要替换库文件即可。

缺点:

  • 静态链接的可执行文件通常比动态链接的文件大,因为它包含了所有必要的库代码。
  • 静态链接更新比较麻烦,需要重新编译整个程序。
  • 动态链接过于依赖外部的动态库,一旦外部的库出现问题,导致很多程序都运行不了。
  • 动态链接的性能开销会比较大,因为需要加载外部的库。

总的来说,动态链接是用时间换空间,静态链接是用空间换时间,如何选择哪种链接方式取决于具体的环境。

这篇关于【基础IO】谈谈动静态库(怒肝7000字)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/921626

相关文章

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

AI基础 L9 Local Search II 局部搜索

Local Beam search 对于当前的所有k个状态,生成它们的所有可能后继状态。 检查生成的后继状态中是否有任何状态是解决方案。 如果所有后继状态都不是解决方案,则从所有后继状态中选择k个最佳状态。 当达到预设的迭代次数或满足某个终止条件时,算法停止。 — Choose k successors randomly, biased towards good ones — Close

Thymeleaf:生成静态文件及异常处理java.lang.NoClassDefFoundError: ognl/PropertyAccessor

我们需要引入包: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>sp

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

C 语言基础之数组

文章目录 什么是数组数组变量的声明多维数组 什么是数组 数组,顾名思义,就是一组数。 假如班上有 30 个同学,让你编程统计每个人的分数,求最高分、最低分、平均分等。如果不知道数组,你只能这样写代码: int ZhangSan_score = 95;int LiSi_score = 90;......int LiuDong_score = 100;int Zhou

c++基础版

c++基础版 Windows环境搭建第一个C++程序c++程序运行原理注释常亮字面常亮符号常亮 变量数据类型整型实型常量类型确定char类型字符串布尔类型 控制台输入随机数产生枚举定义数组数组便利 指针基础野指针空指针指针运算动态内存分配 结构体结构体默认值结构体数组结构体指针结构体指针数组函数无返回值函数和void类型地址传递函数传递数组 引用函数引用传参返回指针的正确写法函数返回数组

Java IO 操作——个人理解

之前一直Java的IO操作一知半解。今天看到一个便文章觉得很有道理( 原文章),记录一下。 首先,理解Java的IO操作到底操作的什么内容,过程又是怎么样子。          数据来源的操作: 来源有文件,网络数据。使用File类和Sockets等。这里操作的是数据本身,1,0结构。    File file = new File("path");   字

【QT】基础入门学习

文章目录 浅析Qt应用程序的主函数使用qDebug()函数常用快捷键Qt 编码风格信号槽连接模型实现方案 信号和槽的工作机制Qt对象树机制 浅析Qt应用程序的主函数 #include "mywindow.h"#include <QApplication>// 程序的入口int main(int argc, char *argv[]){// argc是命令行参数个数,argv是

【MRI基础】TR 和 TE 时间概念

重复时间 (TR) 磁共振成像 (MRI) 中的 TR(重复时间,repetition time)是施加于同一切片的连续脉冲序列之间的时间间隔。具体而言,TR 是施加一个 RF(射频)脉冲与施加下一个 RF 脉冲之间的持续时间。TR 以毫秒 (ms) 为单位,主要控制后续脉冲之前的纵向弛豫程度(T1 弛豫),使其成为显著影响 MRI 中的图像对比度和信号特性的重要参数。 回声时间 (TE)