【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.o
	ar -rc $@ $^
#生成目标文件	
mymath.o:mymath.c
	gcc -c $^
#清理文件
.PHONY:clean
clean:
	rm -rf *.a *.o mylib
#将生成的静态库进行打包
.PHONY:output
output:
	mkdir -p mylib/include
	mkdir -p mylib/mymathlib
	cp *.a mylib/mymathlib
	cp *.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.o
	gcc -shared -o $@ $^
	
mymath.o:mymath.c
	gcc -FPIC -c $^ 

.PHONY:clean

clean:
	rm -rf *.a *.so *.o 

.PHONY:output

output:
	mkdir -p lib/include
	mkdir -p lib/mymathlib
	cp *.so lib/mymathlib
	cp *.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. 动态库加载的原理,进程地址空间程序的加载。

尾序

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

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

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

相关文章

Node.js黑马时钟案例(本人详细介绍实现过程)

先上没有使用node.js之前的html部分代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title></title><style>* {margin: 0;padding: 0;}html,body {height: 100%;overflow: hidden;backgrou…

unity-模块卸载重新安装

unity-模块卸载重新安装 发现模块错误&#xff1f;发现不可以卸载重装&#xff1f;... 依据以下步骤试试&#xff1a; 1. 删除模块文件夹&#xff08;以安卓模块为例&#xff09; 2. 找见编辑器模块json 3. 找见所有安卓相关模块修改selected为false&#xff1a;"sel…

「Verilog学习笔记」边沿检测

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 timescale 1ns/1ns module edge_detect(input clk,input rst_n,input a,output reg rise,output reg down );reg a_tem ; always (posedge clk or negedge rst_n) beginif…

【干货分享】产品经理面试题:你觉得项目管理的职责是什么?

大家好&#xff0c;我是小米&#xff01;今天我要和大家一起聊一个在产品经理面试中常被问到的重要问题&#xff1a;“你觉得项目管理的职责是什么&#xff1f;”别担心&#xff0c;小米来了&#xff0c;一起揭秘项目管理的核心职责&#xff0c;让你在面试中游刃有余&#xff0…

Oracle for Windows安装和配置——Oracle for Windows net配置

2.3. Oracle for Windows net配置 2.3.1. Oracle net配置 2.3.1.1. Oracle net简介 前述章节中,我们只是安装了数据库软件,创建了数据库,测试在服务器本地连接查询数据库。但还不能通过网络远程连接访问数据库,因为我们还没配置用来远程连接访问该数据库的组件Oracle ne…

滚动更新和回滚部署在 Kubernetes 中的工作原理

公众号「架构成长指南」&#xff0c;专注于生产实践、云原生、分布式系统、大数据技术分享。 在过去的几年中&#xff0c;Kubernetes 在生产环境中被广泛使用&#xff0c;它通过其声明式 API 提供了大量解决方案&#xff0c;用于编排容器。 Kubernetes 的一个显著特性是其具有…

CorelDraw2024(CDR)- 矢量图制作软件介绍

在当今数字化时代&#xff0c;平面设计已成为营销、品牌推广和创意表达中不可或缺的元素。平面设计必备三大软件Adebo PhotoShop、CorelDraw、Adobe illustrator, 今天小编就详细介绍其中之一的CorelDraw软件。为什么这款软件在设计界赢得了声誉&#xff0c;并成为了设计师的无…

uniapp的/绝对定位/相对定位/固定定位/粘滞定位

【[html5]你还分不清楚绝对定位和相对定位......】 相对定位一般配合绝对定位使用 <template><view class"content"><view style"background-color: black;width: 100%;height: 300px;position:relative;"><view class"one"…

python爬取快手视频

原理 F12点击graphql能够看到里面有若干视频信息,一会儿要取其中的url地址 右键复制cURL 然后进入到这个转换器连接 https://curlconverter.com/python/ 点击这个连接复制上述信息,然后就能解析处下面的代码,拷贝到你的项目中替换cookies,headers,json_data 源代码 …

【开源】基于JAVA的超市自助付款系统

项目编号&#xff1a; S 008 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S008&#xff0c;文末获取源码。} 项目编号&#xff1a;S008&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 商品类型模块2.2 商品模块2.3 超市账…

「Verilog学习笔记」ROM的简单实现

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 分析 要实现ROM&#xff0c;首先要声明数据的存储空间&#xff0c;例如&#xff1a;[3:0] rom [7:0]&#xff1b;变量名称rom之前的[3:0]表示每个数据具有多少位&#xff0…

面试题 三

一、this 手写call //1、定义myCall方法 //3、接收剩余参数并返回结果 Function.prototype.myCall function (thisArg, ...arg) {// console.log(thisArg); //person对象// console.log(this); //func方法//2、设置this并调用原函数//下面三行代码有个缺陷就是如果pers…

暖阳脚本_ 将Agent技术的灵活性引入RPA,清华等发布自动化智能体ProAgent

RPA暖阳脚本 近日&#xff0c;来自清华大学的研究人员联合面壁智能、中国人民大学、MIT、CMU 等机构共同发布了新一代流程自动化范式 “智能体流程自动化” Agentic Process Automation&#xff08;APA&#xff09;&#xff0c;结合大模型智能体帮助人类进行工作流构建&#x…

Leetcode2937. 使三个字符串相等

Every day a Leetcode 题目来源&#xff1a;2937. 使三个字符串相等 解法1&#xff1a;枚举 设 len1、len2、len3 分别为字符串 s1、s2、s3 的长度。 min_len 是 3 个字符串长度的最小值。 枚举 len min_len 到 len 1&#xff0c;设 t1、t2、t3 分别是字符串 s1、s2、s…

论文笔记:The Impact of AI on Developer Productivity:Evidence from GitHub Copilot

0 abstract 本文介绍了一项对GitHub Copilot&#xff08;一种人工智能编程助手&#xff09;的控制实验结果。研究人员招募了软件开发人员&#xff0c;要求他们尽可能快地用JavaScript实现一个HTTP服务器。实验组可以访问人工智能编程助手&#xff0c;比对照组完成任务的速度快…

JSP 四大域对象

我们来说说JSP的四大域对象 首先 我们要了解他们是四种保存范围 第一种 是 Page范围 只作用于当前界面 只要页面跳转了 其他页面就拿不到了 第二种 request范围 在一次请求中有效 就是 我们服务端指向某个界面 并传递数据给他 那么 如果你是客户端跳转就不生效了 第三种 sessi…

Synchronized 相关面试题 (精简版)

目录 问题一&#xff1a;Synchronized用过吗&#xff0c;其原理是什么&#xff1f; 问题二 : 你刚才提到获取对象的锁&#xff0c;这个“锁”到底是什么?如何确定对象的锁 ? 问题三&#xff1a;什么是可重入性&#xff0c;为什么说 Synchronized 是可重入锁&#xff1f; …

[和ChatGPT学编程]Python Requests 简介

requests 是一个流行的 Python 库&#xff0c;用于发送 HTTP 请求。它提供了简洁而友好的 API&#xff0c;使得发送 HTTP 请求变得简单而直观。requests 具有许多强大的功能&#xff0c;适用于各种 HTTP 请求场景&#xff0c;包括 GET、POST、PUT、DELETE 等。 目录 requests 库…

系列五、线程间通信

一、synchronized实现 1.1、案例一&#xff08;2个线程交替对变量执行1、-1操作&#xff0c;来10轮&#xff09; 1.1.1、资源类ShareDataOne /*** Author : 一叶浮萍归大海* Date: 2023/11/20 10:44* Description: 资源类* 说明&#xff1a;2个线程使用if判断变量的值&#…

EfficientPhys

研究背景 基于相机的生理测量是一种非接触式方法&#xff0c;用于通过从身体反射的光捕获心脏信号。最常见的此类信号是通过光电体积描记图 (PPG) 测量的血容量脉搏 (BVP)。由此&#xff0c;可以推导出心率、呼吸率和脉搏传导时间。神经网络模型是当前最先进的 rPPG 测量方式。…
最新文章