【基础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还是会优先考虑链接动态

动静态链接的优缺点

优点:

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

缺点:

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/560761.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】内存函数-memcpy-memmove-memset...用法及实现,沉淀自己!

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 目录 1. memcpy函数使用和模拟实现 2. memmove使用和模拟实现 3. memset函数的使用 4. memcmp函数的使用 1. memcpy函数使用和模拟实现 <string.h>-------…

机器学习理论基础—神经网络算法公式学习

机器学习理论基础—神经网络公式学习 M-P神经元 M-P神经元&#xff08;一个用来模拟生物行为的数学模型&#xff09;&#xff1a;接收n个输入(通常是来自其他神经 元)&#xff0c;并给各个输入赋予权重计算加权和&#xff0c;然后和自身特有的阈值进行比较 (作减法&#xff0…

pytorch-MNIST测试实战

这里写目录标题 1. 为什么test2. 如何做test3. 什么时候做test4. 完整代码 1. 为什么test 如下图&#xff1a;上下两幅图中蓝色分别表示train的accuracy和loss&#xff0c;黄色表示test的accuracy和loss&#xff0c;如果单纯看train的accuracy和loss曲线就会认为模型已经train…

【优质书籍推荐】Vue.js+Node.js全栈开发实战

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。…

von Mises-Fisher Distribution (代码解析)

torch.distribution 中包含了很多概率分布的实现&#xff0c;本文首先通过均匀分布来说明 Distribution 的具体用法, 然后再解释 von Mises-Fisher 分布的实现, 其公式推导见 von Mises-Fisher Distribution. 1. torch.distribution.Distribution 以下是 Uniform 的源码: cl…

怎么使用JMeter进行性能测试?

一、简介 JMeter是Apache软件基金会下的一款开源的性能测试工具&#xff0c;完全由Java开发。它专注于对我们应用程序进行负载测试和性能测量&#xff0c;最初设计用于web应用程序&#xff0c;现在已经扩展到其他测试功能&#xff0c;比如&#xff1a;FTP、Database和LDAP等。…

Vue 3 项目构建与效率提升:vite-plugin-vue-setup-extend 插件应用指南

一、Vue3项目创建 前提是已安装Node.js&#xff08;点击跳转Node官网&#xff09; npm create vuelatest这一指令将会安装并执行 create-vue&#xff0c;它是 Vue 官方的项目脚手架工具。你将会看到一些诸如 TypeScript 和测试支持之类的可选功能提示&#xff1a; ✔ Projec…

跟着Carl大佬学leetcode之977 有序数组的平方

来点强调&#xff0c;刷题是按照代码随想录的顺序进行的&#xff0c;链接如下https://www.programmercarl.com/本系列是记录一些刷题心得和学习过程&#xff0c;就看到题目自己先上手试试&#xff0c;然后看程序员Carl大佬的解释&#xff0c;自己再敲一遍修修补补&#xff0c;练…

electron打包dist为可执行程序后记【electron-quick-start】

文章目录 目录 文章目录 前言 一、直接看效果 二、实现步骤 1.准备dist文件夹 2.NVM管理node版本 3.准备electron容器并npm run start 4.封装成可执行程序 1.手动下载electron对应版本的zip文件&#xff0c;解决打包缓慢问题 2.安装packager 3.配置打包命令执行内容…

【点云语义分割】弱监督点云语义分割自适应标签分布

Adaptive Annotation Distribution for Weakly Supervised Point Cloud Semantic Segmentation 摘要&#xff1a; 弱监督点云语义分割因其能够减轻对点云细粒度注释的严重依赖而备受关注。然而&#xff0c;在实际应用中&#xff0c;稀疏注释通常在点云中呈现出明显的非均匀分布…

Table表格(关于个人介绍与图片)

展开行&#xff1a; <el-table :data"gainData" :border"gainParentBorder" style"width: 100%"><el-table-column type"expand"><template #default"props"><div m"4"><h3>工作经…

封装个js分页插件

// 分页插件类 class PaginationPlugin {constructor(fetchDataURL, options {}) {this.fetchDataURL fetchDataURL;this.options {containerId: options.containerId || paginationContainer,dataSizeAttr: options.dataSizeAttr || toatalsize, // 修改为实际API返回的数据…

Flutter 的 showDialog 和 showCupertinoDialog 有什么区别?

我将我的 App 里用的 Flutter 升级到了 3.19&#xff0c;没想到&#xff0c;以前我用 showDialog 和 AlertDialog 组合创建的二次确认框&#xff0c;变得无敌难看了&#xff0c;大幅度增加了整个框的圆角和里面默认按钮的圆角。不得已&#xff0c;我必须修改一下&#xff0c;以…

51.基于SpringBoot + Vue实现的前后端分离-校园志愿者管理系统(项目 + 论文)

项目介绍 本站是一个B/S模式系统&#xff0c;采用SpringBoot Vue框架&#xff0c;MYSQL数据库设计开发&#xff0c;充分保证系统的稳定性。系统具有界面清晰、操作简单&#xff0c;功能齐全的特点&#xff0c;使得基于SpringBoot Vue技术的校园志愿者管理系统设计与实现管理工…

偏微分方程算法之二维初边值问题(紧交替方向隐格式)

目录 一、研究对象 二、理论推导 2.1 二维紧差分格式 2.2 紧交替方向格式 2.2.1 紧Peaceman-Rachford格式 2.2.2 紧D’Yakonov格式 2.2.3 紧Douglas格式 三、算例实现 四、结论 一、研究对象 继续以二维抛物型方程初边值问题为研究对象: 为了确保连续性,公式…

Vue.extend()和我的两米大砍刀

Vue.extends是什么&#xff1f; 一个全局API,用于注册并挂载组件。 传统的引用组件的方式是使用import直接引入&#xff0c;但是使用Vue.extends()也可以实现。 使用规则 <div id"mount-point"></div>// 创建构造器 var Profile Vue.extend({templat…

PyCharm,终端conda环境无法切换的问题(二个解决方案)

问题 PyCharm终端&#xff0c;环境切换无效&#xff0c;默认始终为base 解决一 Settings->Tools->Terminal->ShellPath&#xff0c;将powershell修改为cmd.exe 解决二 conda config --show在输出中找到 auto_activate_base 的行&#xff0c;发现被设置为 true&#x…

R语言中的execl数据转plink

文章目录 带出外部连接的方式添加列的方式从列表中选出对应的数据信息查看变量信息没有成功 带出外部连接的方式 点击这个黄色的按钮就可以弹出外部链接的方式 添加列的方式 创建一个数据框的方式 我们创建一个三行三列的数据方式 df <- data.frame(name c("Alice&…

嵌入式linux中利用QT控制蜂鸣器方法

大家好,今天给大家分享一下,如何控制开发板上的蜂鸣器。 第一:开发板原理图 从原理图中可以得出,当引脚输出低电平的时候,对应的蜂鸣器发出响声。 第二:QT代码详细实现 设置一个按钮,点击即可控制BEEP状态发生反转。 #ifndef MAINWINDOW_H #define MAINWINDOW_H#in…

【网络编程】TCP流套接字编程(TCP实现回显服务器)

一.TCP流套字节相关API. Socket(既能给客户端使用,也能给服务器使用) 构造方法 基本方法: ServerSocket(只能给服务器使用) 构造方法: 基本方法: 二.TCP实现回显服务器. 客户端代码示例: package Demo2;import java.io.IOException; import java.io.InputStream; import j…