Linux——动静态库

news2024/11/18 3:33:07
在进行开发过程中,我们不可避免地会使用到人家的库,那么库到底是什
么?而库又分为动态库和静态库,那么这两个又是什么?这篇博客由我来
简单介绍动静态库。

文章目录

  • 1. 库
  • 2. 静态库
    • a. 静态库的制作
    • b. 使用静态库
  • 3. 动态库
    • a. 动态库的制作
    • b. 动态库使用
    • c. 使用动态库的前置准备
  • 4. 动态库的加载
  • 5. 再次认识虚拟地址空间

1. 库

我们进行大型开发的时候,会有许多个头文件和原文件,并且也会使用C语言的标准库,还有或多或少的第三方库。当我们写出这样的程序:
在这里插入图片描述
我们知道它编译是使用了C语言的标准库,而我们包了stdio.h这个头文件,按常识来说,我们也因该会有他的原文件,而我们看到他源文件存放的地方找到他时,我们找不到.c为后缀的。但是可能会找到以lib开头.so或者.a.和某些版本号这样的形式的文件。而这样的文件就是库,而库分为动态库(Linux中.so后缀)和静态库(Linux中.a后缀)。库大致是我们所使用的其他原文件的集合目的,而使用动静态库的开发也提高了开发效率和安全性。这里的说明只是大致的说明,接下来我会详细的介绍。

2. 静态库

我们在开发过程中会建立多个头文件和原文件,比如制作一个简陋般的计算器:
在这里插入图片描述
在这里插入图片描述
我们使用gcc编译它们:
在这里插入图片描述
这里在编译时不需要加上头文件的原因是,gcc会自动在系统默认路径和当前路径下寻找需要的头文件。这样编译我们感觉还行,但是如果我们的原文件有100个呢?当我们把我们的头原文件交给他人使用的时候,其他人使用也是添加这么多原文件编译吗?会不会太费劲了?而且如果我们使用这么多的原文件需要交叉使用生成不同的可执行呢?每次都把他们重新编译一边吗?

a. 静态库的制作

所以这时候我们其实可以把所有的原文件处理成可重定向二进制文件,也就是将这些文件都处理成.o文件,这样最起码我们形成多个可执行的时候,不需要再重新编译浪费时间了,而只需要链接需要的文件就可以了:

在这里插入图片描述
而现在假如把我们写的头文件和.o文件交付给他人使用比交付给.c文件安全性高且效率高:
当用户使用时,只需要将他的原文件和发过来的.o文件编译链接就可以了:
在这里插入图片描述
我们这样使用是传过来的原文件较少,如果有许多个呢?这时候库就出现了。我们可以将这些.o文件进行打包这样使用的时候就没有那么麻烦而只用包相应的头文件就可以了,将.o文件打包的命令是ar命令:
在这里插入图片描述
其中的-rc选项意思是当生成文件已经存在时替换它,不存在则生成。
这样就做好了我们的静态库,现在我们将我们的静态库把User文件中的.o文件替换掉:
在这里插入图片描述
但是这样的格式是不太标准的,我们知道C标准库将头文件放在include目录下,库文件放在lib64目录下,我们也可以这样做:
在这里插入图片描述

b. 使用静态库

使用静态库需要使用到gcc的三个选项:
在这里插入图片描述
-I(大写i):让gcc找头文件的时候也在这个路径下找
-l(小写L):后面跟库的真实名字(去掉lib前缀和.a/.so后缀)
-L:后跟链接库的路径
这样就可以使用我们的静态库了,有人就会问了,编译链接动态库的时候gcc还的加个选项啊-static,这个选项表明的意思其实是:使用的库必须全部以静态库链接,如果链接的库只有动态库则直接报错,而我们的gcc默认编译链接是动静态混合的
静态库链接加载的方式是直接将内容拷贝到我们的可执行中

3. 动态库

在实际开发中我们的动态库是使用的较多的。

a. 动态库的制作

动态库的制作也是将.o文件打包,但是它是使用gcc来打包,并且打包时候的.o文件是经过处理的,我们会用到一个选项:-shared。处理.o文件时的选项-fPIC。
在这里插入图片描述
我们也像制作静态库标准那样来:
在这里插入图片描述
交付给用户:
在这里插入图片描述
这样一个动态库就制作好了。

b. 动态库使用

动态库的使用和静态库差不多:
在这里插入图片描述
但是当我们运行后;
在这里插入图片描述
它说找不到我们的动态库。原因就是动态库的加载是当我们的可执行程序成为进程后,动态库也会加载到内存中,而我们的可执行中只存储着动态库的位置,并把它加载到内存中。所以当我们的可执行使用动态库的时候就需要找到他,这里说它找不到的原因是,可执行程序找动态库只会在它的默认路径下找:
在这里插入图片描述
这里有个ldd命令,会显示可执行链接的静态库的情况。
所以我们要使用动态库还需要一些前置准备:

c. 使用动态库的前置准备

1. 将获取到的第三方库下载到我们的系统中

也就是将我们的头文件放到include中,将动态库放到lib64中:
在这里插入图片描述
而当这么做之后,我们以后有关链接这个动态库的编译,就不需要说明其他,而只需要说明库的真实名字即可。 在这里插入图片描述这也是使用第三方库最推荐的做法。

2. 当前目录/lib64下建立动态库的软链接

在这里插入图片描述
建立软链接:
在这里插入图片描述
在这里插入图片描述

3.环境变量

在Linux中有一个环境变量LD_LIBRARY_PATH。
在这里插入图片描述

它存储着让可执行找静态库的默认路径。所以只要添加我们的路径到它这里面也可以:
在这里插入图片描述
更改环境变量;
在这里插入图片描述
在这里插入图片描述

4. 更改配置文件

在Linux的一个路径下存着这么一些文件:
在这里插入图片描述
这些以.conf结尾的文件存储着可执行文件查找动态库时的路径:
在这里插入图片描述
所以我们也可以把我们的动态库所在路径添加到这里:
在这里插入图片描述
在这里插入图片描述
如果还是找不到的话,可以使用sudo ldconfig命令刷新一下配置文件。

以上就是我们动静态库的制作和使用,如果你写了一个比较好的库供人使用的话,那么就可以用以上格式,压缩之后传输给别人,别人解压缩安装好你的库就可以直接使用了。

4. 动态库的加载

我们要知道,我们写好代码之后经过编译器编译它就不存在什么变量名和函数名了,统统都变成了二进制,而这种二进制也不是杂乱无章的,它是有规则的,在Linux下,它遵循ELF格式。进行着有规则的数据分布,而他的数据分布大致如下:
在这里插入图片描述

可以看到它跟虚拟地址空间的的分布有些相似。这不是偶然现象,而是,ELF文件的格式就是虚拟地址空间的格式,因为栈和堆都需要动态分配,所以没有这两个分布。由此可以看出虚拟地址空间不只是一种技术,也是一套标准。
其中符号表就是记录了代码中所使用的函数与地址的映射关系。汇编代码中对函数的调用我们可以发现是通过call一个地址来跳转的,也就是说,程序没被运行的时候,可执行文件中就已经有了地址。而既然在未加载进内存中的时候就已经有了地址,那这个地址是什么呢?其实它就是虚拟地址,因为由于上面ELF文件存储数据的格式,导致它对文件编址的时候也基本遵循虚拟地址空间的方式。而这种地址在磁盘中就已经有了,所以它也叫逻辑地址,我们一般叫这个名字。
由于ELF可执行文件的编址符合虚拟地址,那么在32位机器下,它的可编址范围是从0到FFFFFFFF,那我们就可以将我们的代码进行编址了,它的编址大致如下:
在这里插入图片描述
在这其中补充个小知识点:我们上面使用到的地址其实都可以看成是从0开始然后加上一个偏移量(例如0+11223344)的方式,而这种以0为基地址,然后+偏移量寻址的方式叫做平坦模式。而ELF文件的编码都是以0为基地址,不会改变,所以我们可执行文件的编址方式也叫做绝对编址

我将代码简略表示了一下,在我们的main函数中只调用了printf函数和用户自己定义的Add函数,由于Add是我们属于我们自己代码的函数,所以当编译的时候直接就把它硬编址,给编好了地址,而我们的printf函数是库函数,而这个库是动态库,动态库又不会加载到我们的程序中,所以我们对它的编址先持保留。这个时候我们的可执行文件已经编译好了。当我们运行这个文件的时候,系统知道我们会使用到C标准库中的函数,当我们运行我们的可执行文件时,这个动态库当进程需要的时候这个动态库就已经在内存中了(因为函数的定义还是在动态库中)。所以我们的C标准库需要加载进内存中,但是我们的进程在执行代码的时候使用的并不是物理内存的地址,而是虚拟内存的地址啊。所以我们动态库要跟我们进程的虚拟地址空间产生联系,而虚拟地址空间中,共享区正好是建立这个“联系”的地方,而这种联系也是通过页表建立(会详细说明):
在这里插入图片描述
这里就要注意了,我们的可执行只会使用一个动态库吗?肯定不是的,它可能会调用许多个库。而这些库在某些时候会使用它,有些时候又不使用了,我说明的意思是这个共享区中动态库的“联系”是时刻变化的,那既然它在变化,说明某个动态库建立好“联系”后,又断开了然后又建立了,那么这次的建立和第一次建立“联系”的位置在共享区中是一个位置吗?结果肯定是不一定了,有可能被其他动态库在原来那个位置建立了。那么我们就需要动态库在进程地址空间中共享区建立联系的的位置是想在哪里建立就在哪里建立
这个“联系”中有一个重要的参数其中就是动态库在虚拟地址空间的地址
而动态库也是需要对其中的内容编址的呀,它这个时候还能使用平坦模式,还能以绝对编址的方式对代码编址吗?要知道动态库在虚拟地址空间的地址不是固定的,它的开头可不是0。动态库它就是为了进程而服务的。这个时候动态库就采用了相对编址的方式来对代码进行编制:
在这里插入图片描述
这样不管我们的动态库在地址空间中哪个地址中建立联系,只要库目前已经加载到地址空间中,那么它的地址也就确定了。我们只需要那个地址+所需函数的偏移量就能找到所需函数了。这就是相对编址。我们也发现了这种相对所选择的参考系是虚拟地址空间,而如果是库本身的话,那他也是绝对编址。
那么现在调用printf函数的时候我们的过程就如下:
在这里插入图片描述
这也就是为什么我们需要动态库的可执行程序运行时需要告诉动态库的信息了,因为它也要加载进内存中供进程使用使用的方式还是自身加载到共享区中的地址+目标函数的偏移量,来调用函数。这也是gcc 编译形成
.o文件时,需要使用位置无关选项了。而只使用静态库的程序不需要告诉进程静态库的信息原因就是你可以理解为,静态库在链接时,就相当于把自己的代码拷贝到了我们的程序代码中。
以上是我们运行一个程序,假如我们运行一百个程序呢?这一百个程序都用到了printf函数。那么这个时候如果程序使用动态库的话,还需要重复加载C标准库到内存中吗?显然是不用了,,因为只要各自进程中,建立与内存中已经加载好的那一个C标准库建立联系就可以了。所以这也是动态库为什么更受欢迎的原因,一方面是它可以用编译器gcc直接打包生成,另一方面,也节省了内存。而这也是为什么虚拟地址空间中存动态库的区域也叫做共享区了,动态库也叫做共享库。一个进程需要用到多个库,多个进程用到更多的库,那么库也需要被管理,那管理的方式自然还是先描述后组织了。

5. 再次认识虚拟地址空间

经过上面动态库的加载原理,我们现在再来看看我们的程序到底是怎么运行的。
我们知道我们的程序中存在地址,那么当我们的程序加载到内存中创建PCB,建立虚拟地址空间,成为一个进程时,我们的代码在物理内存中,物理内存需不需要对代码形成的指令的进行编址?答案肯定也是需要的。我们虚拟地址空间的分布那自然就把可执行文件的逻辑地址搬过来就成了我们的虚拟地址了。CPU执行我们的指令时,他肯定会从main函数开始,那main函数是怎么被找到呢?遍历吗?其实ELF文件格式中会记录main函数的地址,叫做entry,供操作系统读取。
CPU中会有一个指令寄存器来处理指令,要执行指令,首先要找到指令,那么这时候找指令使用的是虚拟地址还是物理地址呢?其实是虚拟地址。这样当CPU找指令的时候使用虚拟地址然后通过页表,找到指令的物理地址(我们说了物理内存也是需要对指令进行编址的)然后开始执行指令,这时候如果是调用函数的话,那么它的指令是call 一个地址,而这个指令又是在文件中的地址,所以这个地址肯定也是虚拟地址,CPU又会使用这个虚拟地址,来通过页表找到函数然后继续执行。这样我们的CPU好像在虚拟地址空间、页表、物理内存上转起来了。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NFTScan 与 OneID 达成合作伙伴,支持多类型 DID 搜索!

近日,NFT 数据基础设施 NFTScan 与一体化数字身份解决方案 OneID 达成合作伙伴关系,双方将在 NFT 数据层面展开合作。为 Web3 用户带来优质的 NFT 搜索查询交互体验,向更安全和更有效的去中心化生态系统迈出的重要一步。 NFTScan 浏览器现已支…

2024.1.30

快速排序降序 #include<stdio.h> #include<string.h> #include<stdlib.h> int quick_sort(int arr[],int low,int high) {//基准值int keyarr[low];int low1low,high1high;if(low>high) return 0;while(low<high) {//high开始比较while(low1<high1…

React Router 完美教程(上)

概述 什么叫路由呢&#xff0c;说白了就是如何处理页面的跳转。在传统的网站中&#xff0c;我们都是向服务器请求页面及相应的css和js代码。自从前后端分离的相思提出后&#xff0c;一堆基于js虚拟Dom的框架应运而生。React就是其中优秀的代表作之一。这种方式极大的优化了开发…

怎么判断一个链表是否成环?怎么找到成环的起点

问题引入 给定一个单向链表&#xff0c;怎么判断这个链表是否成环&#xff1f;如果这个链表是环形的&#xff0c;找到这个环形的起点。 Getter Setter public class ListNode {public Integer val;public ListNode next;public ListNode(Integer val) {this.val val;}public…

嵌入式学习第十四天

1.结构体&#xff08;2&#xff09;: &#xff08;1&#xff09;结构体类型定义 &#xff08;2&#xff09;结构体变量的定义 &#xff08;3&#xff09;结构体元素的访问 &#xff08;4&#xff09;结构体的存储: 内存对齐: char 按照1字节对齐 …

DDD学习使用

简介 DDD(Domain-Driven Design)&#xff1a;领域驱动设计。 Eric Evans “领域驱动设计之父” DDD不是架构&#xff0c;而是一种方法论&#xff08;Methodology&#xff09;微服务架构从一出来就没有很好的理论支撑如何合理的划分服务边界&#xff0c;人们常常为服务要划分多…

java面向对象基础(面试)

一、面向对象基础 1. 面向对象和面向过程的区别 面向过程把解决问题的过程拆成一个个方法&#xff0c;通过一个个方法的执行解决问题。面向对象会先抽象出对象&#xff0c;然后用对象执行方法的方式解决问题。 2.创建一个对象用什么运算符?对象实体与对象引用有何不同? n…

坚持刷题 | 完全二叉树的节点个数

Hello&#xff0c;大家好&#xff0c;我是阿月&#xff01;坚持刷题&#xff0c;老年痴呆追不上我&#xff0c;今天刷&#xff1a;完全二叉树的节点个数 题目 222.完全二叉树的节点个数 代码实现 class TreeNode {int val;TreeNode left, right;public TreeNode(int val) …

1990-2021年各省绿色金融指数数据(含原始数据+测算结果)

1990-2021年全国各省绿色金融指数数据&#xff08;含原始数据结果&#xff09; 1、时间&#xff1a;1990-2021年 2、指标&#xff1a;地区、年份、该省环保项目信贷总额&#xff08;亿元&#xff09;、全省信贷总额&#xff08;亿元&#xff09;、绿色信贷、环境污染治理投资…

记录 arm 开发板上 nginx 配置 http 服务注意事项

1. 自定义项目&#xff0c;需要在 conf.d 目录中增加一个 .conf 配置文件&#xff1a; server {listen 9200; # 端口号server_name localhost; # 服务名称location / {root /home/imx6q/media; # 项目根目录&#xff08;需要修改 n…

41、WEB攻防——通用漏洞XMLXXE无回显DTD实体伪协议代码审计

文章目录 XXE原理&探针&利用XXE读取文件XXE带外测试XXE实体引用XXE挖掘XXE修复 参考资料&#xff1a;CTF XXE XXE原理&探针&利用 XXE用到的重点知识是XML&#xff0c;XML被设计为传输和存储数据&#xff0c;XML文档结构包括XML声明、DTD文档类型定义&#xf…

Elasticsearch Windows版安装配置

Elasticsearch简介 Elasticsearch是一个开源的搜索文献的引擎&#xff0c;大概含义就是你通过Rest请求告诉它关键字&#xff0c;他给你返回对应的内容&#xff0c;就这么简单。 Elasticsearch封装了Lucene&#xff0c;Lucene是apache软件基金会一个开放源代码的全文检索引擎工…

OpenAI API 的最新动态:新一代的嵌入模型,更新 GPT-4 Turbo,更新 GPT-3.5 Turbo 以及降低 API 价格

文章目录 一、前言二、主要内容三、总结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 OpenAI 正在推出新一代嵌入模型、新的 GPT-4 Turbo 和审查模型、新的 API 使用管理工具&#xff0c;而且很快就会降低 GPT-3.5 Turbo 的价格。 OpenAI…

latex中的一些小要点

表格 1.生成双直线表格 \begin{tabular}{| l | l |}⬇ ⬇ ⬇ ⬇ ⬇ ⬇\begin{tabular}{| l || l |}\hline ⬇ ⬇ ⬇\hline \hline →

Java基于SpringBoot+Vue的电影影城管理系统,附源码,文档

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

【PyQt】04-Designer

文章目录 前言一、初级 Designer1.1 拖拽设计界面1.2 搞定之后记得保存ui文件1.3 载入代码1.4 运行结果 二、登入界面代码效果展示账号密码错误时账号和密码正确 总结 前言 自然还是跟着王铭东老师学的 一、初级 Designer 1.1 拖拽设计界面 进度条是这个 1.2 搞定之后记得保…

布局技巧及CSS初始化

一&#xff0c;margin负值巧妙应用 二&#xff0c;文字围绕浮动元素 三&#xff0c;行内块 四&#xff0c;CSS三角强化 五&#xff0c;CSS初始化 一&#xff0c;margin负值巧妙应用 制作盒子的细线边框&#xff1a; 鼠标经过li后变色&#xff1a; 二&#xff0c;文字围绕…

3D 转换

1&#xff0c;3D的特点&#xff1a; 近小远大 物体后面遮挡不可见 2&#xff0c;3D移动 translate3d 3D移动在2D移动的基础上多加了一个可以移动的方向&#xff0c;就是z轴方向 transform&#xff1a;translateX&#xff08;100px&#xff09;&#xff1a;仅仅是在x轴上移动…

Redis冲冲冲——缓存三兄弟:缓存击穿、穿透、雪崩

目录 引出缓存击穿缓存穿透缓存雪崩 总结 引出 谈谈redis的击穿、穿透、雪崩。 缓存击穿 缓存击穿&#xff1a;redis中没有&#xff0c;但是数据库有 顺序&#xff1a;先查缓存&#xff0c;判断缓存是否存在&#xff1b;如果缓存存在&#xff0c;直接返回数据&#xff1b;如果…

考研高数(共轭根式)

1.定义 共轭根式&#xff1a;是指两个不等于零的根式A、B&#xff0c;若它们的积AB不含根式&#xff0c;则称A、B互为共轭根式。 共轭根式的一个显著特点是通过相乘能把根号去掉&#xff0c;这是很有帮助的 2.常用的共轭根式 3.例题 1&#xff09;求极限 2&#xff09;证明…