当前位置: 首页 > news >正文

【Linux进阶之路】动静态库

文章目录

  • 回顾
  • 一. 静态库
    • 1.代码传递的方式
    • 2.简易制作
    • 3.原理
  • 二. 动态库
    • 1.简易制作
    • 2.基本原理
  • 尾序

回顾

  前面在gcc与g++的使用中,我们简单的介绍了动态库与静态库的各自的优点与区别:

  1. 动态链接库,也就是所有的程序公用一份代码,虽然方便省空间,但是一旦链接库被删,那么所有的程序将无法运行!
  2. 静态链接库,就是所有程序都拷贝一份代码自己用,这样虽然库删除之后会正常运行,但是会使代码的空间异常的大,通常在几十倍到几百倍左右。
  • 详见——基本开发工具

 那么今天就让我们通过动静态库的制作过程与基本原理,进而更深一步了解动静态库吧!

一. 静态库

1.代码传递的方式

  • 我们要想让别人使用我们写的代码,有两种方式:
  1. 将源文件与头文件直接发给别人。
  2. 将源文件打包成的库与头文件发给别人。

区别:

  1. 第一种相当于把实现方法直接发给别人,别人可以进行抄袭与学习以及使用,几乎是把自己的劳动成果(实现方法)拱手让人。
  2. 第二种相当于只把说明书(头文件)发送给了别人,由于打包成了库,因此实现方式别人看不到,只能进行使用。
  • 总结:类比商品,假如你买了个电脑,第一种是只附带有说明书,外加实现的具体方法。第二种是只附带了说明书。因此买的电脑如果是第一种,电脑用坏了,可以自己修,甚至可以自己再造出一台电脑。如果是第二种,用坏了,自己还得去找人家花钱修。
  • 重点:不管哪种方式,头文件必不可少!因为头文件是一份使用说明书,一些具体的使用细节都在头文件中。而且在代码中包含头文件才能用里面的接口。如果不这样使用方式及其麻烦。

2.简易制作

  • 源文件
#include"mymath.h"
int myerrno = 0;
int add(int x,int y)
{return x + y;
}
int sub(int x,int y)
{return x - y;
}
int product(int x,int y)
{return x * y;
}
int div(int x,int y)
{if(y == 0){myerrno = 1;return -1;}return x / y;
}
  • 头文件
//存放的是函数与变量的声明
extern int myerrno;
int add(int x,int y);
int sub(int x,int y);
int product(int x,int y);
int div(int x,int y);

说明:静态库的名称格式为——libXXX.a

生成静态库的指令:

argc -rc [静态库的名称] [要生成静态库的目标文件]
  • Makefile
#定义静态库变量的名称
lib=libmymath.a
#目标文件生成静态库, $(lib)为变量
$(lib):mymath.oar -rc $@ $^
#生成目标文件	
mymath.o:mymath.cgcc -c $^
#清理文件
.PHONY:clean
clean:rm -rf *.a *.o mylib
#将生成的静态库进行打包
.PHONY:output
output:mkdir -p mylib/includemkdir -p mylib/mymathlibcp *.a mylib/mymathlibcp *.h mylib/include
  1. make 生成 静态库与.o文件

在这里插入图片描述
2. make output将静态库与头文件进行拷贝打包

在这里插入图片描述
3. make clean 将多余的文件进行清理

在这里插入图片描述

此时我们的静态库就打包好了,下面我们另起文件进行使用。


与mylib同目录下编写test.c

#include"mymath.h"
#include<stdio.h>
int main()
{printf("myerrno:%d 1 + 0 = %d\n",myerrno,add(1,0));printf("myerrno:%d 1 - 0 = %d\n",myerrno,sub(1,0));printf("myerrno:%d 1 * 0 = %d\n",myerrno,product(1,0));printf("myerrno:%d 1 / 0 = %d\n",myerrno,div(1,0));return 0;
}

我们编译一下:
在这里插入图片描述

  • 可见我们所包含的头文件不在当前目录与默认路径(usr/include),因此找不到。

因此我们需要告诉编译器,去哪找。

gcc test.c -I ./mylib/include

因为头文件已包含文件名,因此不用再说明。

在这里插入图片描述

  • 可见我们函数定义还没有包含,因此找不到定义

因此我们需要告诉编译器,去哪找库。

gcc test.c -I ./mylib/include -L ./mylib/mymathlib/

在这里插入图片描述

  • 可见因为库名字未知且一个目录下可能有多个库,因此我们还找不到定义。

因此我们需要告诉编译器,库名字(库真实的名字为去掉后缀.a 与前缀 lib)。

 gcc test.c -I ./mylib/include -L ./mylib/mymathlib/ -l mymath

在这里插入图片描述

  • 总结
  1. -I(大写 i) 指定头文件的路径
  2. -L指定库所在路径
  3. -l(小写 L) 指定库的名称。且库的名称是去掉 lib 与 .a后缀。

3.原理

  1. 时间:在预处理,编译,反汇编,生成.o(可重定向目标二进制文件)之后。
  2. 动作:将静态库里面的内容,拷贝, 与.o文件一起链接生成的.exe文件。
  • 说明:链接进行段表的合并,符号表的重新定位,其中段表的合并是把有效信息筛选无效信息删除,符号表的重新定位指的时检查代码是否正确,比如函数与某些全局变量的地址是否是有效的。

二. 动态库

1.简易制作

我们还是用之前的代码(将myerrno删了)。

两个关键动作:

  1. 生成.o文件并生成位置无关码
	gcc -FPIC -c mymath.o
  1. 生成动态库
	gcc -shared -o libmymath.so mymath.o
  • Makefile
lib=libmymath.so$(lib):mymath.ogcc -shared -o $@ $^mymath.o:mymath.cgcc -FPIC -c $^ .PHONY:cleanclean:rm -rf *.a *.so *.o .PHONY:outputoutput:mkdir -p lib/includemkdir -p lib/mymathlibcp *.so lib/mymathlibcp *.h lib/include
  1. make生成动态库与.o文件
    在这里插入图片描述

  2. make output 动态库与.h文件进行打包
    在这里插入图片描述

  3. make clean 删除冗余的动态库文件与.o文件
    在这里插入图片描述


同理,我们使用一下库,验证一下。

  • test.c
#include"mymath.h"
#include<stdio.h>int main()
{printf("1 + 1 == %d\n",add(1,1));printf("1 - 1 == %d\n",sub(1,1));printf("1 * 1 == %d\n",product(1,1));printf("1 / 1 == %d\n",div(1,1));return 0;
}

同理我们直接使用之前静态库的结论进行编译链接。

gcc -o test test.c -I lib/include/ -L /lib/mymathlib/ -l mymath

在这里插入图片描述

补充: ldd 【可执行文件】 #显示与可执行文件链接的
  • 可见在生成可执行程序是没问题的,但是显示无法打开这个共享文件对象,这是问什么呢?

解释:

  1. 在动态链接时,我们是在可执行程序变成进程运行的同时,链接到对应库当中,其中库是文件,需要打开才能被链接。
  2. 因此需要让加载器去指定的路径下打开文件,才能使用动态库。
  • 注意:前面的gcc 只是让编译器解决了如何找的问题,如何让加载器打开还没有解决。其次静态链接因为是直接拷贝,因此无需关心打开的问题。

因此:我们需要将让编译器想办法在默认路径下打开库文件。


  1. 直接拷贝到默认路径(最常用)
    在这里插入图片描述
  • 可见是链接成功的,可执行程序也能正常的执行,不过因为要拷贝到系统的路径下,所以我们需要sudo 进行提权。
  1. 在默认路径下建立对应静态库的软链接
    在这里插入图片描述
  • 与第一种方式同理,唯一需要说明的是对不在同一目录下建立软链接,需要使用绝对路径,而不是相对路径。
  1. 修改环境变量LD_LIBRARY_PATH(可能会没有)
    在这里插入图片描述
  • 说明:只需要后跟:与动态库所在的路径即可。至于名称我们在链接形成可执行程序时,已经知道了。
  • 注意:这里环境变量在重启时,就没有了,这是比较恶心的一点。
  1. 添加配置文件
  1. su / su - 切换到root用户
  2. 进入 /etc/ld.so.conf.d/
  3. 添加一个.conf结尾的任意名称的文件
  4. vim 此文件,切换到 Insert模式,添加动态库的路径,保存并退出。
  5. 使用 ldconfig更新此配置文件。
  • 图解:在这里插入图片描述
  • 验证:在这里插入图片描述

2.基本原理

 先来铺垫一下,我们编译器与链接器处理代码的过程:

  1. 预处理,完成头文件的替换,条件编译中代码的裁剪,宏的替换等。
  2. 预编译,完成对语义分析,词法分析,语法分析,符号汇总等,检查语法错误,最终转换为汇编代码。
  3. 汇编,完成符号表与段表的生成,并将代码转换为二进制代码。
  4. 链接,完成符号表的重定位,与段表的合并,并生成可执行程序。

那可执行程序里面存放的是什么呢?

我们反汇编一下:

objdump -S [可执行程序]

在这里插入图片描述

  • 可见是一些指令级别的东西,这里我们或许还能勉强看懂一些汇编,里面还存放着地址。
  • 因此我们可以从中得知,可执行程序在还没有被加载时就已经存在地址了。

那么问题来了,这里的地址是物理地址还是虚拟地址?

  • 肯定是虚拟地址,是要给进程地址空间使用的,物理地址是操作系统在程序加载之后申请的。

这是编译的结论,接下来我们的程序是如何加载到内存当中的呢?

 我们先就可执行程序来进行讨论,我们编译好的可执行程序是在磁盘当中的,在加载时必然要被加载到内存当中。

 对于操作系统来说可执行程序在加载时必然要变为进程,之前我们已经了解过进程是 PCB数据结构, 以及代码和数据。 其中PCB在Linux中为stuct tasks_struct 包含着 页表, 进程地址空间(struct mm_struct) 管理文件的(struct files_struct)等对象。

 那在程序加载时,必然要先形成进程,代码和数据可以后面用时再加载。那进程的地址空间首先要先加载,才能保证后续的正常运行。

至于进程的地址空间的加载,我们用图辅助理解:
在这里插入图片描述

  • 代码在进行加载时,通过页表其指令在进程地址空间中是虚拟地址,也就是编译生成的地址,而实际执行指令的物理地址在加载时就通过页表进行填充。这样进程便可通过指令的虚拟地址通过页表获取到指令的物理地址,进而执行指令。
  • 除此之外,加载时,要想找到可执行程序,还得进程的exe,即可执行程序的路径。这种信息在进程加载时即可进行获取。

 代码现在成功加载到内存中了,那指令是如何运行的呢?

首先万事开头难,如何读取到程序的第一行指令很关键,因此会设置程序入口地址以便接下来的执行

其次CPU首先通过指令寄存器拿到指令的虚拟地址,然后通过页表进行映射,成物理地址,然后根据指令的具体信息,进行执行,然后接着执行下一句代码,如此循环往复。

  • 说明:在加载中,程序指令原本的虚拟地址在内存中变为了物理地址,而原来的虚拟地址则给了进程地址空间,这样才讲的通。

其次数据我们可以在需要时加载,在加载时,触发缺页中断,让操作系统将页表进行填充即可。


代码与数据如何加载我们已经讲的差不多了,那动态库是如何加载的呢?

  • 在这之前我们已经达成了共识,动态库是共享库,即多个进程都可以使用。

那么便可大致画出:
在这里插入图片描述

  • 可见动态库是在加载过程中与进程产生链接的。

那链接到进程地址空间的什么位置呢?

  • 进程地址空间的共享区,这个共享区很大,足够跟多个动态库进行链接。

既然在可能有多个共享库进行链接,那么如何进行链接,才能保证能找到指定的共享库呢?

  1. 我们可以采用起始地址 + 偏移量的方式,从而使函数在在找库时,只需知道偏移量即可。
  2. 偏移量的设定与动态库生成中的位置无关码有关。
  • 拓展:在进行链接时,动态库也可能会产生缺页中断的现象,即用时再进行加载。

补充:

  1. 第三方库,即自己写的库,在进行链接时,必须要指定库名字!
  2. 如果一个库的方法实现有动态库,也有静态库,那么默认优先加载动态库。

  • 总结
  1. 静态库的原理与简易制作。
  2. 动态库的原理与简易制作。
  3. 动态库加载的原理,进程地址空间程序的加载。

尾序

 如果有所帮助的话,不妨点个赞鼓励一下吧!

相关文章:

【Linux进阶之路】动静态库

文章目录 回顾一. 静态库1.代码传递的方式2.简易制作3.原理 二. 动态库1.简易制作2.基本原理 尾序 回顾 前面在gcc与g的使用中&#xff0c;我们简单的介绍了动态库与静态库的各自的优点与区别&#xff1a; 动态链接库&#xff0c;也就是所有的程序公用一份代码,虽然方便省空间&…...

Ubuntu磁盘扩展容量

gparted扩展...

2023年中国羽绒制品需求现状、市场规模及细分产品规模分析[图]

羽绒羽毛指生长在水禽类动物&#xff08;鹅、鸭&#xff09;腋下、腹部羽绒和羽毛的统称&#xff0c;属于上游鹅鸭肉食品工业副产品的综合利用&#xff0c;是下游羽绒制品的填充料。根据国家标准&#xff0c;绒子含量≥50%的称为羽绒&#xff0c;绒子含量&#xff1c;50%的称为…...

动手学深度学习——循环神经网络的从零开始实现(原理解释+代码详解)

文章目录 循环神经网络的从零开始实现1. 独热编码2. 初始化模型参数3. 循环神经网络模型4. 预测5. 梯度裁剪6. 训练 循环神经网络的从零开始实现 从头开始基于循环神经网络实现字符级语言模型。 # 读取数据集 %matplotlib inline import math import torchfrom torch import …...

【操作系统】文件系统的逻辑结构与目录结构

文章目录 文件的概念定义属性基本操作 文件的结构文件的逻辑结构文件的目录结构文件控制块&#xff08;FCB&#xff09;索引节点目录结构 文件的概念 定义 在操作系统中&#xff0c;文件被定义为&#xff1a;以计算机硬盘为载体的存储在计算机上的信息集合。 属性 描述文件…...

局域网内Ubuntu上搭建Git服务器

1.在局域网内选定一台Ubuntu电脑作为Git服务端&#xff1a; (1).新建用户如为fbc&#xff0c;执行如下命令&#xff1a;需设置密码&#xff0c;此为fbc sudo adduser fbc (2).切换到fbc用户&#xff1a;需密码&#xff0c;此前设置为fbc su fbc (3).建一个空目录作为仓…...

基础课10——自然语言生成

自然语言生成是让计算机自动或半自动地生成自然语言的文本。这个领域涉及到自然语言处理、语言学、计算机科学等多个领域的知识。 1.简介 自然语言生成系统可以分为基于规则的方法和基于统计的方法两大类。基于规则的方法主要依靠专家知识库和语言学规则来生成文本&#xff0…...

xpath

xpath 使用 使用 from lxml import etree或者 from lxml import htmlet etree.XML(xml) et etree.HTML(html) res et.xpath("/book") # 返回列表项目Valueet.xpath(“/book”)/表示根节点/div/a子节点用/依次表示/name/text()text()取文本/book//nick//表示标签…...

Java拼图小游戏

Java拼图小游戏 import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collections; import java.util.List;public cla…...

终于有人把数据资产入表知识地图总结出来了,轻松看懂

在当前数字化的浪潮下&#xff0c;数据已经成为劳动、土地、知识、技术以后的第五大生产要素&#xff0c;“数据就是资源”已成为共识。如今数据资产“入表”已成定局&#xff0c;数据资产化迫在眉睫。 2023年8月21日&#xff0c;财政部正式印发《企业数据资源相关会计处理暂行…...

白鳝:聊聊IvorySQL的Oracle兼容技术细节与实现原理

两年前听瀚高的一个朋友说他们要做一个开源数据库项目&#xff0c;基于PostgreSQL&#xff0c;主打与Oracle的兼容性&#xff0c;并且与PG社区版内核同步发布。当时我听了有点不太相信&#xff0c;瀚高的Highgo是在PG内核上增加了一定的Oracle兼容性的特性&#xff0c;一般也会…...

vue和uni-app的递归组件排坑

有这样一个数组数据&#xff0c;实际可能有很多级。 tree: [{id: 1,name: 1,children: [{ id: 2, name: 1-1, children: [{id: 7, name: 1-1-1,children: []}]},{ id: 3, name: 1-2 }]},{id: 4,name: 2,children: [{ id: 5, name: 2-1 },{ id: 6, name: 2-2 }]} ]要渲染为下面…...

【考研】数据结构(更新到顺序表)

声明&#xff1a;所有代码都可以运行&#xff0c;可以直接粘贴运行&#xff08;只有库函数没有声明&#xff09; 线性表的定义和基本操作 基本操作 定义 静态&#xff1a; #include<stdio.h> #include<stdlib.h>#define MaxSize 10//静态 typedef struct{int d…...

汇编-指针

一个变量如果包含的是另一个变量的地址&#xff0c; 则该变量就称为指针(pointer) 。指针是操作数组和数据结构的极好工具&#xff0c;因为它包含的地址在运行时是可以修改的。 .data arrayB byte 10h, 20h, 30h, 40h ptrB dword arrayB ptrB1 dword OFFSET arrayBarray…...

常见Web安全

一.Web安全概述 以下是百度百科对于web安全的解释&#xff1a; Web安全&#xff0c;计算机术语&#xff0c;随着Web2.0、社交网络、微博等等一系列新型的互联网产品的诞生&#xff0c;基于Web环境的互联网应用越来越广泛&#xff0c;企业信息化的过程中各种应用都架设在Web平台…...

milvus数据库搜索

一、向量相似度搜索 在Milvus中进行向量相似度搜索时&#xff0c;会计算查询向量和集合中具有指定相似性度量的向量之间的距离&#xff0c;并返回最相似的结果。通过指定一个布尔表达式来过滤标量字段或主键字段&#xff0c;您可以执行混合搜索。 1.加载集合 执行操作的前提是…...

HEVC参考帧技术

为了增强参考帧管理的抗差错能力&#xff0c;HEVC采用了参考帧集技术&#xff0c;通过直接在每一帧的片头码流中传输DPB中各个帧的状态变化&#xff0c;将当前帧以及后续帧可能用到的参考帧在DPB中都进行描述&#xff0c;描述以POC作为一帧的身份标识。因此&#xff0c;不需要依…...

QT小记:The QColor ctor taking ints is cheaper than the one taking string literals

这个警告意味着在使用 Qt 的 C 代码中&#xff0c;使用接受整数参数的 QColor 构造函数比使用接受字符串字面值的构造函数更有效率。 要解决这个警告&#xff0c;你可以修改你的代码&#xff0c;尽可能使用接受整数参数的 QColor 构造函数&#xff0c;而不是字符串字面值。例如…...

机器人走迷宫问题

题目 1.房间有XY的方格组成&#xff0c;例如下图为64的大小。每一个方格以坐标(x,y) 描述。 2.机器人固定从方格(0, 0)出发&#xff0c;只能向东或者向北前进&#xff0c;出口固定为房间的最东北角&#xff0c;如下图的 方格(5,3)。用例保证机器人可以从入口走到出口。 3.房间…...

轻量封装WebGPU渲染系统示例<36>- 广告板(Billboard)(WGSL源码)

原理不再赘述&#xff0c;请见wgsl shader实现。 当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/BillboardEntityTest.ts 当前示例运行效果: WGSL顶点shader: group(0) binding(0) var<uniform> objMat :…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学&#xff08;ECC&#xff09;是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础&#xff0c;例如椭圆曲线数字签…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...