【Linux】基础 IO(动静态库)-- 详解

一、前言

为什么要使用别人的代码?

主要是为了提高程序开发的效率和程序的健壮性。

当别人把功能都实现了,然后我们再基于别人的代码去做二次开发,那么效率当然就提高了。其次,这里基于的别人当然不是随便找的一个人,而特指的是顶尖的工程师,也就是说如果我们的代码出了问题,一般不会去怀疑是别人的库的问题,这也就增加了代码的健壮性。

换而言之,如果我写我的代码,别人写别人的代码,那么在机制上我们的工作就得以分解,假如我是一个写调用、逻辑特别严谨的程序员,假设别人是写库写得很好的一个人,我们两个都有比较完善的工作方式,那么两个人合起来就可以提高代码的效率和健壮性,而不需要两方面都兼顾。

程序的健壮性,更直观的理解是我们在百度搜索时输入乱码,而百度却不会崩溃。


  • 动态库:libc.so,libc++.so
  • 静态库:libc.a,libc++.a

一般在命名上去掉前缀 lib,去掉 . 和之后的内容,剩下的就是库名,所以这里就是 C 库和 C++ 库。

C/C++ 体系中如何使用别人的功能?  

生成可执行的方式有静态链接和动态链接,对应的是静态库和动态库。
比如,张三是一名大一新生,他在写作业时突然有了一个上网的需求,但因为周边环境不熟悉,所以找了学长询问附近网吧的地点,随后就跑去玩了几个小时,然后回来后接着继续写作业,这就叫作动态链接。两年后张三已经是一名大三师兄了,要开始准备找工作了,家里给他买了一台电脑,当他正在学习时想要上网就不用再特意跑去网吧了,而只需要打开自己的电脑就行,也就是说静态链接并没有和外界产生关联。
一般我们写的程序中大部分都是动态链接,因为动态链接中不需要把库中的内容进行过多的拷贝,所以相对而言,它的编译效率较高,这是其一;我们有时候下载一些软件,比如 VS2019,它上面会有一些组件,比如 C++、C# 等,但这些组件并没有在我们的硬盘中,而当我们要安装时,它会帮我们找到这些组件下载,这样有个好处就是我们需要什么就直接下载什么,而不是直接一堆东西直接装在机器上,而自己需要使用的组件却寥寥无几,所以这里就采用动态库的方式实现,这是其二。

当然静态链接也有属于自己的使用场景,一般在服务器上大部分也都是动态链接,不过有时候需要将服务在很多机器上部署,那么单纯的动态链接就可能会出问题,因为动态链接是在程序运行之后才去对应加载到内存中,万一有个库丢失了,那么程序就挂了。所以有时候一些程序它也会采用静态链接,好处就是它不依赖于任何动态库,坏处就是效率比较低。比如静态链接的大小是动态链接的一百倍,要把静态链接这个程序下载下来就只能全部下下来,而动态链接则是边下边用。总的来说,两者各有利弊,没有绝对的好坏之分。

  • 动态链接的生成的可执行程序的体积往往比较小,节省资源(磁盘、内存),但是它依赖第三方库,有一定的风险,一旦库丢失,可执行程序不可执行(网吧被查封了)。
  • 静态链接虽然生成的可执行程序的体积较大,浪费资源(磁盘、内存),但是它不依赖第三方库,一旦库丢失,可执行程序依旧可以执行(网吧被查封了照样可以玩)。

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。

在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)。

动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。


二、静态库的制作打包与使用(不使用 makefile)

1、准备工作

别人要使用这些接口最直观的就是直接给源文件,别人就可以直接在当前目录创建文件,然后直接包含它们就行。但是如果不想让别人看到我们的源文件是如何实现的,而只是告诉别人它们的作用,那么就需要使用库。其实动静态库的属性就是不想暴露自已的源代码,所以可以直接打包给别人。

我们让别人来使用我们的库,前提是别人需要知道我们的库能给他们提供什么方法,这是通过头文件来体现的(头文件可以暴露)。但光有头文件还不行,里面只有声明,还需要有实现,所以就将方法所在的源文件进行编译然后打包到库。


2、生成静态库

(1)gcc -c mymath.c -o mymath.o / gcc -c myprint.c -o myprint.o

先将源文件汇编后生成 .o 文件,它虽然也是二进制文件,但是它还不可以执行,因为还差最后一步链接。所以库就是在编译过程中,在链接的前一步停下来,编译成可被链接的目标文件。


如果只把 .h 和 .o 给别人,别人可以用吗?

可以。


(2)ar -rc libhello.a mymath.o myprint.o

ar (Archive) 是  gnu  归档工具, rc  表示 replace and create,意思就是如果要生成的库中已经包含了对应的 .o 文件,就 replace,否则就 create

此时 libhello.a 就是静态库,所以我们将来只需要把 mymath.h,myprint.h,libhello.a 打包交付给其它人即可,而 libhello.a 就由 mymath.h,myprint.h 来说明。这也就是为什么大部分库在提供的时候,一般是提供库文件 + 头文件,也就是为什么在使用 C 语言的时候永远都是 #include <stdio.h>,然后在写 printf 的时候直接调用,最后链接库,本质上就是因为系统在装的时候就把库文件和头文件给装了,而 C 语言的源代码就不需要在系统中了。就相当于把源文件先编译一大部分,不要链接,再把所以编译好的 .o 文件打个包让别人去用,这就是静态库。

提示:libmymath.a 前缀必须是 lib。

(3)查看静态库中的目录列表

  • t:列出静态库中的文件
  • v:verbose 详细信息

3、使用静态库

李四想用张三写好的 mymath,myprint 代码,但是张三不想让李四看到自己是怎么实现的,所以就将它们打包好变成库 hello,复制给李四,李四人拿到后就可以通过头文件知道了这个库是干嘛的:


需要提前将库拷贝到系统的默认路径下,这个过程叫作库的安装。

所以李四就在 main.c 中使用张三写好的库,随后 gcc main.c -o main,报错说 myprint.h 是没有这个文件或目录的,原因是 myprint.h 既没有在当前路径下,也没有在默认路径下,这里的当前路径是指要和 main.c 在同一级路径下。所以这里 gcc 还要再加一个选项 -I,后面跟上 ./hello,意思就是 gcc 在编译程序,找头文件的话,除了在当前路径下,系统路径下,你也要在 ./dir 下去找。

此时还有报错,但对比上一次报错,显然头文件已经找到了。此时报的是在链接时找不到 addToTarget 方法,所以还 gcc 还要在加上一个选项 -l,后面跟上 ./hello,表明库文件在这个目录下。只不过这里是巧合,头文件和库文件都在 ./hello下。

然后又报错了,原因是指明了库路径,但并没有指明要访问的是这个路径下的哪一个库。如果这个路径下有多个库就需要指明了。所以还要再加一个选项 -l,后面跟上 libhello.a 去掉前后缀,也就是 hello。此时就完成了链接,形成了可执行程序:


(1)-I + 路径

告诉 gcc 除了默认路径以及当前路径,在指定路径下也找一下头文件。而一般 linux 下头文件的默认路径在 /usr/include 下,其中我们就看到了最熟悉的 C 文件 stdio.h。


(2)-L + 路径

告诉 gcc 除了默认路径以及当前路径之外,在指定的路径下也找一下库文件。而一般 linux 下库文件的默认路径在 /lib64 或 /usr/lib64 下, 其中你往下翻,也可以找到比较熟悉的 C 语言的静态库和动态库,libc.a 和 libc.so。

这里需要说一下,实际系统在搜索头和库的时候,除了这些默认路径,还有其它的路径。另外不同 linux 发行版,甚至同一发行版在路径上都可能不太一样,所以具体问题具体对待。


(3)-l + 库名

需要进一步具体说明要链接哪一个库。


4、库搜索路径

  • 从左到右搜索 -L 指定的目录。
  • 由环境变量指定的目录(LIBRARY_PATH)
  • 由系统指定的目录
  1. /usr/lib
  2. /usr/local/lib

为什么 C/C++ 在编译的时候,从来没有明显的使用过 -I/L/l 等选项呢?

库文件和头文件在默认路径下 gcc 可以找到。

gcc 编译 C 代码, 默认就应该链接 libc 库。


如果我们自己也不想使用这些选项呢?

头文件和库文件分别拷贝到系统的默认路径下,这个过程就叫做库的安装。我们之前学过环境变量 PATH,然后写了个 Hello World,让它不使用路径就可以执行,其实是把可执行程序拷贝到环境变量的路径下,其实就是安装可执行程序。

不过我们自己写的库叫做第三方库,也就是除了系统,语言之外的库。所以一般也要带上 -l name 表明你要链接的是哪一个库。


这里为什么不需要指定静态链接的方式?

因为当前库中只有静态库,所以这里就算不写 static,也只能用静态的。也就是说,有动态库和静态库时,gcc 编译默认是动态库,但只有静态库的时候就只能是静态库。


三、动态库的制作打包与使用(使用 makefile)

1、准备工作

这里打包动态库依旧不想把源文件暴露出去。  


2、生成动态库

  • shared:生成共享库格式
  • fPIC:产生位置无关码(position independent code)
  • 库名规则:libxxx.so

在栈区和堆区中间有一个共享区,一般动态库的代码是映射在这个区域,库文件当然也是一个文件,当然也占磁盘空间。当程序运行,其中需要执行库中的代码时,本质就是进程来执行库中的代码。换而言之,进程一旦运行起来,同时也要把库加载到内存,当然也可以局部加载,然后就把库映射到共享区,此时代码区的代码就可以直接访问共享区中的库。这样做的一个好处就是,如果有多个进程时,可以统一把要使用的一个库从内存映射到自己的共享区,这样就相当于可执行程序是不需要携带库代码的,从而可以有效的节省资源。
如果是静态链接,形成进程后就没有用共享区,此时代码区中就包含了你的代码和库的代码,如果有 10 个 C 代码,每个程序都把库代码都拷贝一份到代码区,此时在内存中就会有 10 份重复的库代码,而实际只需要 1 份即可。所以动态库最典型的特点就是,所有和我们使用同一种库的进程可以把库从内存映射到共享区,以节省内存资源。
其次,有可能 A 进程共享区中只是一部分区域映射到 C 库,B 进程共享区中也只是一部分区域映射到 C 库,但不管最终物理到虚拟地址是如何映射的,可执行程序加载到物理内存的任何位置,最后都一定要保证库中产生的各种代码与我们这个库加载到内存中的位置和映射到共享区的位置是没有关系的,这就叫做产生与位置无关码,如果深入进一步了解就需要学习编译原理中可执行程序的格式。这里可以简单化的理解为,因为库是随时随地可能加载的,它也可能在内存中的任何位置,也可能被映射到共享区的任何区域,所以必须保证库中的代码不会出错。比如有一万行执行代码,因为它本身与位置无关,代码中出现了一个函数调用,它在编译时地址是 0x1234,加载到内存,映射后函数的地址相对于调用方的地址发生了变化,代码就可能执行不起来了,所以必须保证调用目标函数的地址它本身是不会随着程序的加载位置以及映射区域的位置变化而变化的,这就是产生位置无关码。
比如有一条跑道,李四距离终点 80m,张三距离终点 100m,而当终点往前移动 20m 后,此时李四和张三原来距离终点的距离就不对了,但无论终点怎么变,张三距离李四有 20m 是不变的,这就是与位置无关码。
最后再说一下,这个动态库是不会随着本身加载到共享区的任意位置而影响到库中代码地址发生变化,而导致库中的代码不可执行的,所以 gcc 一定要用 fPIC 选项来产生与位置无关码。


3、使用动态库

张三打包好库 output,李四拿到了库:

然后李四写好代码就开始编译,这里如同静态库一样需要使用 -I,-L,-I 选项,然后 ./main 时报错说不能打开共享文件,原因是没有这个文件或目录,为什么呢?刚才不是很明显的把头文件在哪里,库文件在哪里,库文件的名字是什么告诉了 gcc,怎么现在又不认识了?

此时 ldd 确实也是有个库找不到,刚才的选项是给编译器看的,而此时 ./main 时已经和 gcc 没有关系了,所以这里是运行时的问题,所以在运行的时候也要能让系统帮我们找到运行时需要使用的动态库。

那为什么之前动态链接的其它程序可以直接运行呢?因为运行时别人的库可以被找到,它们在默认路径下。这里有一些方案,这里只推荐这一种:这里有一个环境变量 LD_LIBRARY_PATH,在祼机器上是没有这个环境变量,但如果你曾经做过 vim 配置,可能就会有。如果想让你的库被运行起来,需要把库路径导入其中。这里推荐导入绝对路径,导入成功再 ldd,发现这个库已经能找到了。此时就可以 ./main。

如果动静态库同时存在,默认采用的是动态库。


如果动静态库同时存在,但非要使用静态库呢?

-static:摒弃默认优先使用动态库的原则,而是直接使用静态库的方案。

当目录下同时存在动静态库,那么 gcc 在编译时默认就是动态链接,而使用 static 后就是静态链接。


4、运行动态库

  1. 拷贝 .so 文件到系统共享库路径下,一般指 /usr/lib
  2. 更改 LD_LIBRARY_PATH
  3. ldconfig 配置 /etc/ld.so.conf.d/,ldconfig 更新

在动态库操作的时候,需要把动态库所在的路径导入到环境变量 LD_LIBRARY_PATH,但是重新登录时,自己导入的环境变量的路径就没了。(不推荐把变量导入到登录脚本)

若想让它每次在重新登录上的时候不会被重新配置,可以把库的路径导入到 /etc/ld.so.conf.d/ 目录下。这个配置文件是永久生效的,导入成功后需要 ldconfig 刷新,不过也不推荐这种方案,原因是你写的库导入到这个目录下可能也会污染系统本身的环境变量信息。除非将来你需要导入第三方开源的库。


5、使用外部库

系统中其实有很多库,它们通常由一组互相关联的用来完成某项常见工作的函数构成。比如用来处理屏幕显示情况的函数(ncurses  库)。
  • -lm 表示要链接 libm.so 或者 libm.a 库文件。

6、库文件名称和引入库的名称

如: libc.so -> c 库,去掉前缀  lib ,去掉后缀  .so,.a

为什么要有库?
  • 站在使用库的角度,库的存在可以大大减少我们开发的周期,提高软件本身的质量。
  • 站在写库的人的角度,库既简单又安全。 

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

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

相关文章

matplotlib绘制统计特征图和分布特征图

文章目录 一、统计特征图绘制1.需求2.代码方法一方法二总结 二、分布特征图绘制1.需求2.代码 一、统计特征图绘制 1.需求 我现在有两个数据集Pdata和Cdata分别在DataFrame对象中&#xff0c;我现在想对这两个数据集进行统计特征分析&#xff0c;并用直方图展示出来。 2.代码…

在线预订酒店房源小程序源码系统平台版 带完整的安装代码包以及搭建教程

近年来&#xff0c;互联网技术的飞速发展推动了各行各业的数字化转型。酒店行业也不例外&#xff0c;传统的酒店预订方式已经无法满足现代旅客的需求。旅客期望能够随时随地通过手机或电脑进行酒店预订&#xff0c;并享受到个性化的服务体验。因此&#xff0c;开发一款功能齐全…

107 在携带请求体的情况下, hutool 将 get 请求转换为了 post 请求

前言 本问题主要是来自于同事 情况大致如下, 同样的代码 一个是测试用例, 一个是生产环境的应用, 访问同一个第三方服务, 参数什么的完全一致 但是 出现的问题就是 测试用例能够拿到正确的对方的响应, 但是 生产环境的应用 却是拿到的对方的报错 然后 我开始以为是 是否…

【进阶五】Python实现SDVRP(需求拆分)常见求解算法——离散粒子群算法(DPSO)

基于python语言&#xff0c;采用经典离散粒子群算法&#xff08;DPSO&#xff09;对 需求拆分车辆路径规划问题&#xff08;SDVRP&#xff09; 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整3. 求解结果4. 代码片段参考 往期优质资源 经过一年多的创作&#xff0c;目前已…

邮箱与Email有何异同?如何正确使用它们?

邮箱与Email之间有何联系&#xff1f;如何正确区分邮箱和Email&#xff1f; 电子邮箱已成为我们日常生活和工作中不可或缺的一部分。而提到电子邮箱&#xff0c;很多人会自然而然地联想到“邮箱”这个词。那么&#xff0c;邮箱与Email之间究竟有哪些异同呢&#xff1f;让AokSe…

【LeetCode每日一题】2312. 卖木头块(DFS记忆化搜索+动态规划)

文章目录 [2312. 卖木头块](https://leetcode.cn/problems/selling-pieces-of-wood/)思路1:用DFS进行记忆化搜索代码&#xff1a;思路2:动态规划代码&#xff1a; 2312. 卖木头块 思路1:用DFS进行记忆化搜索 1.要用DFS深度优先遍历每一种情况。在递归的同时&#xff0c;不断更…

React状态管理Mobx

1 https://zh.mobx.js.org/README.html 2 https://juejin.cn/post/7046710251382374413 3 https://cn.mobx.js.org/refguide/observable.html ​​mobx入门基础教程-慕课网​​ ​​Mobx学习 - 掘金​​ 十分钟入门 MobX & React ​​十分钟入门 MobX & React​​…

[BSidesCF 2019]Pick Tac Toe

[BSidesCF 2019]Pick Tac Toe 首先进行常规的信息收集&#xff0c;尝试几次下三子棋后查看源码发现 此时只需要更改id为r的&#xff0c;将他改为X&#xff0c;我们就胜利了抓包发现&#xff0c;数据通过post提交参数为move&#xff0c;顺便再下一子&#xff0c;抓包更改为move…

迈向容错新时代!PASQAL发布最新技术路线图

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 编辑丨慕一 编译/排版丨沛贤 深度好文&#xff1a;1200字丨8分钟阅读 近日&#xff0c;法国中性原子量子计算公司PASQAL发布了最新技术路线图&#xff0c;概述了其在硬件、业务场景用例及进一…

迁移学习的技术突破与应用前景

目录 前言1 迁移学习技术1.1 原理与分类1.2 主要挑战 2 迁移学习应用2.1 计算机视觉2.2 医疗健康 3 未来展望3.1 推动各领域发展3.2 提高模型泛化能力和效果3.3 在新兴领域中广泛应用 结语 前言 迁移学习作为机器学习领域的重要技术之一&#xff0c;以其能够将从一个任务中学到…

为什么延迟删除可以保证MYSQL 与redis的一致性?

看过很多保持MYSQL 与redis保持一致性的文章都提到了延迟删除&#xff0c;其实脱离任何业务场景的设计都是不切实际的&#xff0c;所以我会本着一个通用的读写场景去分析为什么延迟删除大概率可以保证MYSQL与redis的最终一致。 通常的读写场景 通常在使用redis作为读写缓存时…

蓝桥杯-02-2023蓝桥杯c/c++省赛B组题目

参考 2023 年第十四届蓝桥杯 C/C B组省赛题解 2023蓝桥杯c/c省赛B组题目(最全版)&#xff1a; A&#xff1a;日期统计 这题方法应该很多&#xff0c;没有和别人讨论想法。我的解法思路是&#xff1a;先 load 函数生成所有这一年的合法日期&#xff0c;然后枚举所有可以从数据…

嵌套循环实现九九乘法表

大家好&#xff1a; 衷心希望各位点赞。 您的问题请留在评论区&#xff0c;我会及时回答。 案例描述 利用嵌套循环&#xff0c;实现九九乘法表。 代码 #include <iostream> #include <Windows.h>using namespace std;int main(void) {//外层循环执行一次&#…

《计算机考研精炼1000题》为你考研之路保驾护航

创作背景 在这个充满挑战与竞争的时代&#xff0c;每一位考生在备战研究生考试的过程中&#xff0c;都希望通过更多符合考纲要求的练习题来提高自己的知识和技能。为了满足这一需求&#xff0c;我们精心策划和编辑了这本《计算机考研精炼1000题》。在考研政治和考研数学领域&a…

格密码从词根词缀和单词起源的角度来介绍一下,commit词根词缀分析:词义发展:现代用法举例:小结:nuance词根词缀分析:词义发展:现代用法举例:小结:

目录 格密码 从词根词缀和单词起源的角度来介绍一下&#xff0c;commit 词根词缀分析&#xff1a; 词义发展&#xff1a; 现代用法举例&#xff1a; 小结&#xff1a; nuance 词根词缀分析&#xff1a; 词义发展&#xff1a; 现代用法举例&#xff1a; 小结&#xff…

微服务高级篇(一):微服务保护+Sentinel

文章目录 一、初识Sentinel1.1 雪崩问题及解决方案1.2 微服务保护技术对比1.3 Sentinel介绍与安装1.4 微服务整合Sentinel 二、Sentinel的流量控制三、Sentinel的隔离与降级四、Sentinel的授权规则五、规则持久化5.1 规则管理模式【原始模式、pull模式、push模式】5.2 实现push…

B端界面不漂亮,所以搞不定客户,这就扯淡了。

在商业领域&#xff0c;产品的外观和用户体验确实对吸引和留住客户起着重要的作用。漂亮的界面设计可以提升用户对产品的好感和信任度&#xff0c;从而增加用户的使用和购买意愿。 虽然贝格前端工场致力于提升B端系统的感官和体验&#xff0c;但是我们依然认为界面美观不美观&…

c语言综合练习题

1.编写程序实现键盘输入一个学生的学分绩点 score&#xff08;合法的范围为:1.0—5.0&#xff09;&#xff0c;根据学生的学分绩点判定该学 生的奖学金的等级&#xff0c;判定规则如下表所示。 #include <stdio.h>int main() {float score;printf("请输入学生的学分…

解决jenkins运行磁盘满的问题

参考&#xff1a;https://blog.csdn.net/ouyang_peng/article/details/79225993 分配磁盘空间相关操作&#xff1a; https://cloud.tencent.com/developer/article/2230624 登录jenkins相对应的服务或容器中查看磁盘情况&#xff1a; df -h在102挂载服务器上看到是这两个文件…

c++的STL(5)-- set和multiset容器

set和multiset容器概述 首先set和multiset这两种容器底层是红黑树(是一种数据结构&#xff0c;是树形结构)实现的。我们前面说到vector,deque容器是插入数据慢&#xff0c;但是读取数据快&#xff0c;list呢恰恰相反&#xff0c;插入数据快&#xff0c;查询慢。但是set和multis…