【Linux】学习-动静态库

动静态库

头文件与库的区别

  • 头文件一般而言,是声明和宏定义。头文件是在预处理阶段使用的

  • 库文件是已经编译好的二进制代码。是一种目标文件,库文件是在链接阶段使用的

  • 对于头文件和库我们可以这样理解,就是头文件提供的是一个函数的声明,并没有这个函数的具体代码,而库就是存放这个函数的具体实现代码

我们在写代码的时候,经常依赖别人给我们提供的函数,比如头文件中的reverse函数,我们在写的时候,这个头文件中包含有这个函数的声明,而这个函数的定义是存在于库中的,使用库的意义在于,我们可以将函数的定义直接编译成二进制,使得别人看不见这个函数的具体实现方法,别人使用时,只需要根据声明中的说明即可使用,这样使得软件作者既实现了发布方便或替换方便,方便程序的部署与开发。也方便了一些发布者不想让别人看到自己写的函数的具体实现方法,保护了代码隐私。当然,库中肯定不止有函数的定义,这里只是举了一个简单的例子,方便理解。

库的种类

库文件是已经编译好的二进制代码,这个二进制代码可以是动态的,如.so;也可以是静态的,如.a

如果是动态的,则最后生成的程序文件在运行时,需要这个动态库的支持,动态库又叫做共享目标文件

如果是静态的,则最后生成的 可执行程序文件运行时可以脱离这个库文件而独立运行。 静态库又叫做可重定位目标文件

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
  • 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
  • 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
  • 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间

目标文件

在生成我们的可执行程序之前,我们的程序一共会经过四个阶段:预处理,编译,汇编,链接

在经过前面三个阶段后,汇编阶段会生成所谓的目标文件,windows下为.obj文件,Linux下为.o文件,目标文件是还没经过链接的文件,也就是说此时的目标文件中,有些符号还未被调整成正确的有效地址,仍然临时地址,如何理解呢?可以理解成,此时你写的程序内使用的reverse函数中还只是只有声明的部分,需要通过找到reverse存在的库的目标文件链接起来,这样才能成功调用reverse函数。

PC端的可执行文件格式主要包括了windows下的PE(Portable Execute)以及linux下的ELF(Execute Linkable Format),而目标文件属于生成可执行程序文件过程中的中间文件,其格式几乎与可执行程序的文件一模一样,因此可以看成是一种类型的文件,只不过是需要再通过最后一个阶段链接阶段将目标文件中各种各样的段合并起来,本质上文件格式并没有改变什么,想要深入了解可以参考一下这篇博客:目标文件详解,而我们的ELF格式的文件也是有分类型的:可重定位文件(relocate file),可执行文件(executable file),共享目标文件(shared object file)

ELF文件格式类型:

  • 可执行文件

    Linux下和windows下的.exe文件,也就是可以直接执行的文件

  • 可重定位文件

    windows下的.obj文件和Linux下的.o文件,也就是需要通过链接后形成可执行程序的目标文件

  • 共享目标文件

    动态链接文件,可以和其他可重定位文件和共享目标文件一起链接,生成新的目标文件

使用readelf -a filename 可以查看目标文件的ELF格式

image-20230929171036475

image-20230929171116225

relocatable:重定位

executable:可执行

静态库的特征、包装与使用

特征

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
  • 如果是静态的,则最后生成的 可执行程序文件运行时可以脱离这个库文件而独立运行。 静态库又叫做可重定位目标文件

因为静态库是直接把依赖的库的代码链接到可执行文件中,也就是在进行链接一步时,把库中需要的内容直接拷贝一份到最终的可执行文件中,比如说你在自己的代码中写的reverse函数,头文件中是只有声明的,链接一步时, 需要进行合并符号表,而reverse函数的定义存在于库中,也就是需要合并库的符号表,若选择了静态库,即静态链接方式,就是把库中的符号表拷贝一份进可执行文件中,使得可执行文件的大小会明显地变得非常大。

静态库的优点:

  • 加载快,可以独自运行

    由于库中的内容已经被你拷贝进了可执行文件中,因此可执行程序执行时不再需要任何依赖,自己就包含了声明与定义,随时都可以自己执行。

静态库的缺点:

  • 占用空间大

    这不仅仅体现在可执行文件的大小上,若多个源文件都是以静态链接的方式去链接同一个静态库,那么一模一样的代码就会被拷贝多次,重复加载,使得磁盘空间也被重复占用。

  • 兼容性与拓展性不佳

    由于静态链接是将静态库拷贝进自己的代码中,那么当库中某一个函数的实现被修改,而可执行文件中保存的是旧的代码,这样就使得可执行文件又要再一次重新编译链接,不方便程序的升级迭代与重新部署。

包装

静态库形成步骤:

  • 先写好 .c文件和 .h 文件

    image-20230930170133510

  • 将 .c 文件编译成目标文件

    image-20230930170340947

  • 使用ar命令生成静态库

    image-20230930203427694

    库名字前缀必须是lib

    -rc -> r(replace) c(creat)

查看静态库中目录列表:

image-20230930203603865

t:列出静态库中的文件

v:verbose 详细信息

  • 将头文件和库文件打包起来

    将所有的头文件放在include目录下,静态库文件放在lib目录下,再将include目录和lib目录放在同一个目录下,这样将此目录发送给其他人即可使用:

    image-20230930204224730

image-20230930204326948

  • 可以使用Makefile文件一次性全部生成:

image-20230930211740970

image-20230930211728479

使用

  • 使用:将mylib文件夹放在main函数所在的目录下

    gcc编译器默认会先在当前路径下搜索头文件,然后再去库路径下搜索,所以需要显性的告诉编译器头文件的路径在哪:使用 -I 选项

    还需要告诉编译器库的路径: 使用 -L 选项

    以及告诉编译器库的名字:使用 -l 选项,使用-l选项时,去掉前缀 lib 和后缀 .o 即可得到库文件名

image-20230930214159654

还可以直接将我们的库直接拷贝到系统路径下,这样编译时就不再需要指定路径,编译器会自动到系统路径下找image-20230930214354470

但不建议这么做,由于自己的库没有经过可靠性验证,尽量不要安装到系统路径下污染库文件

问题

使用file+文件 查看可执行程序所使用的链接方式, ldd+文件命令 查看可执行程序所依赖的库文件:

image-20230930224615546

这就奇了怪了,为什么会是动态链接呢?我们使用ldd查看一下所依赖的库是什么:

image-20230930224701533

我们查看一下所依赖的这个库文件的类型:

image-20230930225120488

原来是一个共享目标文件类型,但我们已经测试过此可执行程序是可以运行的,也就是说,确确实实是已经使用了我们自己写的静态库了,那究竟是怎么回事呢?

其实,一切都要看我们写的程序中所需要依赖的库,由于我们的程序中存在<stdio.h>库中的printf函数,因此是需要用到C语言库的,而我们编译时指定库的路径时,并没有指定C语言库的路径,只指定了我们所需要的Add函数和Print函数所对应的库的路径,也就是说,没有指定路径时,编译器默认是用动态链接的方式使用C语言中的动态库,若我们强行要将所有的依赖文件全部改为静态链接方式也可以,带上选项 -static:

image-20230930225627471

我们可以发现用这种方式将C语言的静态库文件以静态链接方式加载到可执行文件main中,使得main的文件大小非常之大,但我们所用到的仅仅只有printf函数!!这样就造成了空间的浪费!!

image-20230930225946658

比较一个静态库一个动态库的方式下的文件大小,可以发现差距已经达到万级,因此,C语言库的文件还是以动态链接的方法合适!不到万不得已不使用静态链接!

动态库的特征、包装与使用

特征

  • 如果是动态的,则最后生成的程序文件在运行时,需要这个动态库的支持,动态库又叫做共享目标文件
  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

静态库是库的代码与自己的代码一起在代码区来回跳,也就是执行可执行程序时就把库代码拷贝一份到自己的代码区了。动态库是一开始在硬盘中,执行到需要库的代码时,再将库加载进内存中,然后将库中对应所需的函数等方法建立与页表的映射关系,映射到共享区当中,然后代码区执行的时候再跳转到共享区:

image-20230930234353123

动态库的优点:

  • 节省磁盘空间,占用空间小

    动态链接只需要将动态库的内容加载一次到内存中,若多个文件同时都依赖此库也不需要加载多次,并且只需要加载一些符号表信息与相对路径信息即可,不会占用可执行文件的大小空间

  • 兼容性佳,拓展性强

    还是以reverse函数的例子,如果reverse函数的实现改变了,那么只需要改变动态库本身即可,不需要重新编译执行可执行文件,因为他记录的仅仅是他的信息,通过信息去查找函数,若库本身就已经变了,那么下一次通过此信息去寻找时,找到的也是更新后的内容,不影响更新迭代,因此动态库大大方便了程序的升级和部署

动态库的缺点:

  • 依赖性

    由于静态链接的方式是直接将静态库文件加载进自己的可执行文件,因此下一次执行时不管静态库文件还存不存在都不影响使用,但动态链接不一样,由于可执行文件记录的是动态库信息,可以理解成记录的是如何找到动态库在内存中的位置,因此若动态库不存在了,可执行文件也无法正常运行了,具有强依赖性!

  • 复杂性高

    由于动态库的特殊链接方式,因此还需要设计动态链接器等等复杂的技术来辅助,而不像静态库一样粗暴,因此具有一定设计复杂性

包装

动态库形成步骤:直接用Makefile展示最后结果,若不明白回去静态库的形成再吃一遍

  • 先写好.c .h 文件

  • 将.c 文件以fPIC生成无关码方式编译形成.o文件,作用:告诉编译器此目标文件在任何路径都无所谓

  • 直接用编译器使用 -shared选项生成动态库.so 文件

  • 将.h 文件和 .so 文件包装起来

  • 使用到的命令:

  • image-20231001000907828

  • 展示:image-20231001001720435

使用

还是跟静态库使用一样,先把包装好的目录放置main函数所在的目录下,并使用gcc编译器编译时指明头文件路径,库路径以及动态库名字:

image-20231001011438236

问题

但我们发现,此时可执行程序无法运行,并且提示原因是找不到此共享文件目标,这是怎么回事呢?我们再一次通过ldd+文件名来查看

  • image-20231001011630453

此时我们发现提示的是找不到对应的动态库,但我们不是已经指明了路径了吗?这是为什么?

  • 其实,我们使用 -I -L -l选项时,其实是告诉编译器路径,但一旦可执行程序编译完毕,编译器就做完了他自己的事情了,我们要运行可执行程序时,就与编译器没有关系了,而是操作系统的事情,操作系统此时就找不到路径了,静态库不用找是因为库中代码已经被拷贝进自己的代码中了,并不需要再去找了, 所以为了让操作系统能够顺利找到并执行,有以下方法:
  1. 拷贝.so文件到系统共享库路径下,一般在/lib64
  • image-20231001012431517
  • image-20231001013921010

成功正常运行

  1. 使用环境变量LD_LIBRARY_PATH

image-20231001014333560

但这种属于内存级别的环境变量,在下一次重启时就会被清掉了:

image-20231001014434322

35)]

  • [外链图片转存中…(img-55lGQr3v-1707561573535)]

成功正常运行

  1. 使用环境变量LD_LIBRARY_PATH

[外链图片转存中…(img-A6itzueN-1707561573535)]

但这种属于内存级别的环境变量,在下一次重启时就会被清掉了:

[外链图片转存中…(img-yaa4exwB-1707561573536)]

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

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

相关文章

使用Flash download tool进行ESP32固件烧录

背景 为方便分发固件&#xff0c;可在任意电脑上安装烧录软件&#xff0c;直接将固件烧录进 烧录内容 查看vscode上platformio的烧录过程 Writing at 0x00000000... (100 %) Wrote 15104 bytes (10401 compressed) at 0x00000000 in 0.4 seconds (effective 281.3 kbit/s).…

Pandas常用操作记录(更新中)

1.读取文件 import pandas as pd df pd.read_csv(路径) #pd.read_文件格式(路径) 2.读取某列某行&#xff0c;并使用map替换 2.1 直接读取某列数据 在获取到df对象后&#xff0c;可以使用 df.列名 来获取该列数据&#xff0c; import pandas as pd df pd.read_csv(rdat…

电商小程序06用户审核

目录 1 创建自定义应用2 显示待办数量3 创建审核页面4 开发审核功能5 搭建布局6 最终效果总结 上一篇我们讲解了用户注册的功能&#xff0c;用户注册之后状态是待审核&#xff0c;需要管理员进行审核。通常给管理员提供一套PC端的软件进行相关的操作&#xff0c;在低代码中&…

ChatGPT高效提问—prompt常见用法(续篇五)

ChatGPT高效提问—prompt常见用法&#xff08;续篇五&#xff09; 1.1 种子词 ​ 种子词&#xff08;seed word&#xff09;通常指的是在对话中使用的初始提示或关键词&#xff0c;用于引导ChatGPT生成相关回复。种子词可以是一个词、短语或句子&#xff0c;通常与对话的主题…

代码随想录算法训练营第四十八天(动态规划篇之01背包)| 1049. 最后一块石头的重量Ⅱ,494. 目标和

1049. 最后一块石头的重量Ⅱ 题目链接&#xff1a;1049. 最后一块石头的重量 II - 力扣&#xff08;LeetCode&#xff09; 思路 尽量将石头分为重量相同的两堆&#xff0c;这样两堆中的石头相撞之后剩下的石头就会最小。根据之前的01背包理论&#xff1a; 代码随想录算法训…

【Chrono Engine学习总结】3-地型terrain

由于Chrono的官方教程在一些细节方面解释的并不清楚&#xff0c;自己做了一些尝试&#xff0c;做学习总结。 1、关于物体材质 在介绍地型之前&#xff0c;要初步了解chrono中关于材质的一些基本概念。 首先&#xff0c;最基本的材质类是ChMaterialSurface,其进一步包括&…

原生JS使用PrintJs进行表格打印 -- 遇到的问题总结

需求1&#xff1a;表格自动分页之后&#xff0c;表头在每一页都需要显示 html中表头增加 thead 标签 css样式新增&#xff1a; thead {display: table-header-group; /* 这个属性使thead总是在新的page-break之后重新开始 */ }需求2&#xff1a;表格自动分页之后&#xff0c;…

数据可视化之维恩图 Venn diagram

文章目录 一、前言二、主要内容三、总结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 维恩图&#xff08;Venn diagram&#xff09;&#xff0c;也叫文氏图或韦恩图&#xff0c;是一种关系型图表&#xff0c;用于显示元素集合之间的重叠区…

购物|电商购物小程序|基于微信小程序的购物系统设计与实现(源码+数据库+文档)

电商购物小程序目录 目录 基于微信小程序的购物系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户前台功能实现 2、管理员后台功能实现 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码 六、论文参考 七、最新计算机毕设…

VSTO打包Word插件WPS也支持

启动AdvancedInstallerPortable.exe打包软件 选择“加载项” 选择“office加载项”之后点“创建项目” 四、输入自已的插件名和公司名 任选一种包类型 五、选择包的保存位置 勾选“vsto office加载项” 六、选择要打包的项目debug文件夹 选择相应版本 配置相应环境 选择语言 添…

微服务架构RabbitMQ实现CQRS模式

在现代软件开发中,微服务架构和CQRS模式都是备受关注的技术趋势。微服务架构通过将应用程序拆分为一系列小型、自治的服务,提供了更好的可伸缩性和灵活性。而CQRS模式则通过将读操作和写操作分离,优化了系统的性能和可维护性。本文小编将为大家介绍如何在ASP.NET Core微服务…

在 Next 中, ORM 框架 Prisma 使用

Prisma 介绍 Prisma 是一个 ORM 框架&#xff0c;主要用于 Node.js 或 TypeScript 作为后端开发的应用&#xff0c;主要有三部分组成&#xff1a; Prisma Client&#xff1a;自动生成且类型安全的查询构建器&#xff0c;适用于 Nodex.js 和 TS&#xff1b;Prisma Migrate: 迁…

初步探索Pyglet库:打造轻量级多媒体与游戏开发利器

目录 pyglet库 功能特点 安装和导入 安装 导入 基本代码框架 导入模块 创建窗口 创建控件 定义事件 运行应用 程序界面 运行结果 完整代码 标签控件 常用事件 窗口事件 鼠标事件 键盘事件 文本事件 其它场景 网页标签 音乐播放 图片显示 祝大家新…

视频直播系统架构的设计与实现

视频直播系统作为一种实时性强、用户互动性高的应用&#xff0c;其架构设计至关重要。本文将介绍如何设计和实现一个稳定、高性能的直播系统架构&#xff0c;以提供良好的用户体验和可靠的服务。 1. 系统架构概述 - 介绍视频直播系统的整体架构&#xff0c;包括客户端、服务…

01-Spring实现重试和降级机制

主要用于在模块调用中&#xff0c;出现失败、异常情况下&#xff0c;仍需要进行重复调用。并且在最终调用失败时&#xff0c;可以采用降级措施&#xff0c;返回一般结果。 1、重试机制 我们采用spring 提供的retry 插件&#xff0c;其原理采用aop机制&#xff0c;所以需要额外…

SERVLET间通信

在Web应用程序中,应用程序的servlet等各种组件之间可能需要通信以便处理客户机请求。例如,假设Web应用程序中有一个servlet显示组织的版权信息。您可以使用各种servelt通信技术将此servlet的内容纳入到需要显示版权信息的所有其他应用程序servlet中。同样,如果处理请求时发生…

基于JavaWeb的网上订餐项目

点击以下链接获取源码&#xff1a; https://download.csdn.net/download/qq_64505944/88825723?spm1001.2014.3001.5503 Java项目-16 浏览商品&#xff0c;会员登录&#xff0c;添加购物车&#xff0c;进行配送等功能 文件代码功能介绍 1.Src下的java文件存放的我们后端的…

JDK新特性

JDK新特性 函数式接口和Lambda 表达式Stream流操作新日期API操作其他新特性 Lambda 是一个匿名函数&#xff0c;我们可以把 Lambda表达式理解为是一段可以传递的代码&#xff08;将代码 像数据一样进行传递&#xff09;。可以写出更简洁、更 灵活的代码。作为一种更紧凑的代码…

15000+POC漏洞扫描工具

0x01 工具介绍 scan4all拥有15000PoC漏洞扫描&#xff0c;23种应用弱口令爆破&#xff0c;7000Web指纹&#xff0c;146种协议&#xff0c;90000规则Port扫描。集成 vscan、nuclei、ksubdomain、subfinder等&#xff0c;充分自动化进行扫描。是一款Fuzz、HW打点、BugBounty神器…

CSP-202009-2-风险人群筛查

CSP-202009-2-风险人群筛查 解题思路 检查是否经过高危区 (x > x1) && (x < x2) && (y > y1) && (y < y2) 检查坐标是否在高危区域内&#xff0c; !isPassed 确保仅在第一次经过高危区域时增加 pass 计数。如果条件成立&#xff0c;表示…