【Linux】动态库与静态库

news2024/11/27 0:32:07

目录

一、前言

二、静态库与动态库

三、生成静态库

1、生成原理

2、完整过程

3、总结

四、生成动态库

1、环境变量

2、建立软链接

3、配置文件

五、动态库的加载

1、动态库加载的过程

2、动态库地址的理解

3、补充内容


一、前言

 关于动态库与静态库的一小部分前置内容,我曾在文章《编译器 - gcc && 函数库》中有过详细的说明。

 实际上,系统已经预装了C/C++的头文件和库文件。头文件提供方法的说明,库函数提供方法的实现,头和库是要组合在一起使用的。头文件在预处理阶段就引入了,链接的本质就是链接库。

 所以我们在安装开发环境时,实际上就是在安装对应语言配套的库和头文件。在使用编译器时,都会有语法的自动提醒功能,这需要先包含头文件。语法提醒的本质是,编译器会自动将用户输入的内容,不断地在被包含的头文件中进行搜索,自动提醒功能是依赖头文件实现的。

 在写代码时,环境之所以知道代码中哪些地方有语法错误,哪些地方定义变量有问题,是因为编译器工作模式有命令行模式和其他自动化的模式,在用户写代码时,会不断的进行预处理、编译操作,帮助用户不断地进行语法检查。

 库存在的目的是为了提高开发效率。

二、静态库与动态库

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

三、生成静态库

1、生成原理

这里先实现一个简单的加减法程序:

//myadd.h
#pragma once
int myadd(int d1, int d2);

//myadd.c
#include "myadd.h"
int myadd(int d1, int d2)
{
    return d1 + d2;
}

//mysub.h
#pragma once
int mysub(int d1, int d2);

//mysub.c
#include "mysub.h"
int mysub(int d1, int d2)
{
    return d1 - d2;
}

以上程序编写完成后,可以直接把源代码打包给其他程序使用:

 编译运行:

 结果符合预期。


 如果我们想让其他人调用自己程序的一些功能,但是不想把源代码交给其他人,则可以把自己的程序经过预处理、编译、汇编,生成 .o 文件,即可重定位目标二进制文件,交给别人使用。

现在先把实现功能的源代码与使用功能的程序分别分离到 mylib 与 otherPerson 目录中:

 使用指令 gcc -c [源文件] 生成 .o 文件,并把它放到 otherPerson 目录中:

 进入到 otherPerson 目录中后,先把 main.c 文件也使用指令 gcc -c 生成 .o 文件,再与其他 .o 文件进行链接,最后生成可执行文件:

 最终结果符合预期。


为了方便传输 .o 文件,我们通常会对这些文件进行打包。打包生成静态库指令:

ar -rc [lib文件名.a] [*.o]

 把 *.a 文件和 *.h 文件都复制到 otherPerson 中后,编译 main.c 生成可执行程序:

 出现了如上报错,这是因为当有了库之后,要将库引入项目中,必须要让编译器找到头文件与库文件。由于gcc 与 g++ 只认识 C语言 和 C++ 的库,我们自己引入的库属于第三方库,编译器不认识,当然也就找不到。

 因此,必须自己说明需要链接哪一个库:

gcc -o [可执行程序] [被编译文件] -L[库所处路径] -l[库名]

 结果符合预期。

2、完整过程

 有了以上知识,我们已经知道静态库是怎么封装以及交给别人使用的了。但实际上静态库的传输与使用的真正步骤与上面演示的有所不同,具体过程如下:

 形成静态库后,创建一个 include 目录和一个 lib 目录,分别放入 .h 文件与 .a  文件:

 接下来为了让别人使用自己的静态库,则可以把这两个目录打包并上传,供别人下载使用:

 使用静态库指令:

gcc -o [可执行文件] [源代码文件] -I[头文件路径] -L[库文件路径] -l[库文件名称]

 结合静态库编译并运行观察结果:

 结果符合预期。

 如果不想在编译时指明这么一长串的路径,也可以直接把对应的头文件与库文件拷贝到 gcc、g++ 的默认搜索路径下:

 之后就无需再指明头文件与库的路径,只需说明使用第三方库的名字就可以正常编译使用了。

3、总结

自己生成使用第三方库时,需要满足两个条件:

  1. 需要指定的头文件和库文件
  2. 如果库没有默认安装到系统gcc、g++默认的搜索路径下,用户必须指明对应的选项告知编译器:头文件在哪里、库文件在哪里、库文件是什么名字。
  3. 将下载下来的库和头文件,拷贝到系统默认路径下,这就叫做操作系统中库的安装。对于任何软件而言,安装和卸载的本质就是拷贝到系统特定的路径下,或者从特定路径下删除。
  4. 如果我们安装的库是第三方的(第一方是语言,第二方是操作系统调用接口),在使用时,需要用 -l 说明对应库的名字。
  5. 无论我们是从网络中直接下载的库,还是源代码(编译方法)。都会提供一个 make install 安装的命令,这个命令所做的就是安装到系统中的工作。我们安装大部分指令、库等等都是需要 sudo 提权的。

四、生成动态库

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

 在生成动态库 .o 文件时,需要给指令gcc加上 -fPIC 命令选项,产生位置无关码:

 接下来与静态库相同,也需要把 .o 文件打包以便上传。不同之处在于动态库是使用 gcc 指令进行打包:

gcc -shared -o [lib库名.so] [*.o]

  形成动态库后,创建一个 include 目录和一个 lib 目录,分别放入 .h 文件与 .a  文件:

 接下来为了让别人使用自己的动态库,则可以把这两个目录打包并上传,供别人下载使用:

 使用动态库编译后运行观察结果:

 发现程序运行报错,提示无法打开共享对象文件。

 这是因为我们使用 -l -L 选项说明路径,是向编译器说明的,而不是向OS说明。在程序运行的时候,因为 .so 没有在系统的默认路径下,所以OS依旧找不到。

 注意这里与静态库进行区分,静态库这样可以运行是因为,静态库的链接原则是将用户使用的二进制代码直接拷贝到目标可执行程序中,但是动态库不会。

 OS查找动态库有以下几种方法:

  1. 设置环境变量:LD_LIBRARY_PATH。
  2. 在系统指定路径下建立软链接,指向对应的库。
  3. 配置文件。

1、环境变量

  使用 echo 指令查看环境变量 LD_LIBRARY_PATH

 直接使用 export 指令将库的路径添加进该环境变量就可以了:

 此时程序可以正常执行,且结果符合预期。

 因为环境变量在我们退出终端再次登录的时候会被重置,所以我们下一次登陆时就需要重新设置一遍环境变量,否则无法正常运行。

2、建立软链接

 因为库的默认搜索路径是 /usr/lib64 /lib64 。所以我们可以直接挑选一个路径,在该路径下建立对应库的软链接,这里以 /lib64 为例:

编译运行,结果符合预期。

因为软链接是一个正常的文件,永远保存在磁盘上,所以我们退出后再次登录时,程序依然可以正常运行。 

3、配置文件

相关配置文件所处路径: /etc/ld.so.conf.d

这些配置文件中存放库所在的路径。

现在我们自己在此目录下 touch 一个配置文件,并在该配置文件内输入所需库的路径:

 更改完配置文件后,需要让该配置文件生效。采用指令:

ldconfig

 运行程序,结果符合预期。

五、动态库的加载

1、动态库加载的过程

 当使用动态库编译好了一个可执行文件后,该可执行文件存储在磁盘当中,并在运行时加载到内存里。

 我们知道,程序被加载到内存后就变成了进程,OS会在内存中创建对应的 task_struct mm_struct 、 页表 。用户在执行程序中的代码时,正常执行。当需要执行动态库内的代码时,OS会找到动态库,把动态库加载到内存中,并建立页表映射关系,映射到虚拟地址空间的共享区中。这些动作都是由OS自动完成的。

 可执行文件在被编译完成时,就已经具备了对应的虚拟地址。以上动作完成后,再执行动态库内的代码,OS会自动识别,并跳转到虚拟地址的共享区部分,通过页表的映射关系,执行内存中对应的动态库代码,动态库代码执行完毕后,再回到虚拟地址的代码区部分,继续执行下面的其他代码。

 换句话说,只要把库加载到内存,映射到进程的地址空间后,进程执行库中的方法,就依旧还是在自己的地址空间内进行函数跳转即可。

 所以动态库节省资源的原因就在于,动态库里的所有方法在内存里只需要存放一份就可以了,其他所有进程都可以调用。

2、动态库地址的理解

 在程序编译链接形成可执行程序的时候,可执行程序内部就已经有地址了,地址一共有两类,分别是绝对编址与相对编址。

 动态库必定面临一个问题:不同的进程,运行程度不同,需要使用的第三方库是不同的,这就注定了每一个进程的共享区中的空闲位置是不确定的。因此,动态库中函数的地址,绝对不能使用绝对编址,动态库中的所有地址都是偏移量,默认从 0 开始。简单来说,库中的函数只需要记录自己在该库中的偏移量,即相对地址就可以了。

 当一个库真正的被映射到进程地址空间时,他的起始地址才能真正的确定,并且被OS管理起来。OS本身管理库,所以OS知道我们调用库中函数时,使用的是哪一个库,这个库的起始地址是什么。当需要执行库中的函数时,只需要拿到库的起始地址,加上对应函数在该库中的偏移量,就能够调用对应函数了。

 借助函数在库中的相对地址,无论库被加载到了共享区的哪一个位置,都不影响我们准确的找到对应函数。所以这种库被称为动态库,动态库中地址被称为与位置无关码。

3、补充内容

  1.  动态库和静态库同时存在,默认使用动态链接。如果想要使用静态链接,则需要在编译时加上 -static 命令选项。
  2.  如果不提供动态库,只提供静态库,且在编译时不加 -static 命令选项。那么程序在链接时,对于该库,会默认使用静态链接。而对于其他的,比如C库等等,依然默认使用动态链接。

 关于动静态库的相关内容就讲到这里,希望同学们多多支持,如果有不对的地方,欢迎大佬指正,谢谢!

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

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

相关文章

TCP的粘包和拆包

UDP有数据边界,TCP是没有数据边界,是流协议。如何拆包,就要靠应用层来处理。 四层网络模型,消息在进入每一层时都会多加一个报头。mac头部记录的是硬件的唯一地址,IP头记录的是从哪来和到哪去,传输层头记录…

GPT 护理机器人 - 让护士的工作变简单

引子    书接上文《GPT接入企微应用 - 让工作快乐起来》,我把GPT接入了企微应用,不少同事都开始尝试起来了。有的浅尝辄止,有的刨根问底,五花八门,无所不有。这里摘抄几份: “帮我写一份表白信&#xff…

Github 的使用

3. Github 在版本控制系统中,大约90%的操作都是在本地仓库中进行的:暂存,提交,查看状态或者历史记录等等。除此之外,如果仅仅只有你一个人在这个项目里工作,你永远没有机会需要设置一个远程仓库。只有当你…

嗯,这个树怎么和往常不一样?

文章目录 前言一、二叉树的链式存储二、二叉树链式结构的实现二叉树的结构设计手动构建二叉树二叉树的前序遍历二叉树的中序遍历二叉树的后序遍历二叉树的层序遍历计算二叉树大小计算叶子节点个数计算二叉树高度计算第K层的节点个数查找某个值对应的节点二叉树的销毁 三、完整代…

全球首个存量手机直连卫星天地语音通话,打通了!

4月25日,美国卫星通信初创公司——AST SpaceMobile,宣布打通了全球首个天基蜂窝语音通话。 对于卫星通信乃至整个通信行业来说,这是一个重大新闻,非常值得关注。 去年,我们还只是实现了手机和卫星之间的双向短消息通信…

Page管理机制

Page页分类 Buffer Pool 的底层采用链表数据结构管理Page。在InnoDB访问表记录和索引时会在Page页中缓存,以后使用可以减少磁盘IO操作,提升效率 Page根据状态可以分为三种类型: - free page : 空闲page,未被使用 - …

1.3 HBase 基本架构

架构角色: 1)Master 实现类为 HMaster,负责监控集群中所有的 RegionServer 实例。主要作用如下: (1)管理元数据表格 hbase:meta,接收用户对表格创建修改删除的命令并执行 (2&#x…

【C++ Primer(第5版) 课后习题题目及答案 第一章】

C Primer5th 课后习题答案 第一章 1.1:查阅你使用的编译器的文档,确定它所使用的文件命名约定。编译并运行main程序。1.2:改写程序,让它返回-1。返回值-1通常被当作程序错误的标识。重新编译并运行你的程序,观察你的系…

C#弹出消息对话框,增加输入框,接受输入信息

效果图: 代码: using Microsoft.VisualBasic;string intext Interaction.InputBox("请输入密码","输入密码","",Screen.PrimaryScreen.Bounds.Width/4,Screen.PrimaryScreen.Bounds.Height/4);MessageBox.Show("in…

多商户商城系统开发功能有哪些?

多商户商城系统开发功能有哪些? 1、商品管理。商品管理是多商户商城系统的必备功能。商家需要能够轻松地添加、修改和删除商品。这个功能还应该包括商品图片、价格、描述和库存等信息的管理。消费者也需要能够通过小程序浏览商品并下订单。 2、订单管…

图形编辑器:历史记录设计

大家好,我是前端西瓜哥。今天讲一下图形编辑器如何实现历史记录,做到撤销重做。 其实就是版本号的更替。每个版本保存一个状态。 数据结构 要记录图形编辑器的历史记录,支持撤销重做功能,需要两个栈:撤销&#xff0…

【计算机网络】学习笔记:第三章 数据链路层【王道考研】持续更新中....

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ…

《编码——隐匿在计算机软硬件背后的语言》精炼——第14章(边沿触发器,计数器)

学习不是一次性的投资&#xff0c;而是一份长期稳定的收益。 文章目录 8位锁存器边沿触发器计数器改进的边沿触发器 8位锁存器 上篇文章讲到了1位存储器的组成&#xff0c;将8个1位存储器的时钟端连在一起就形成了一个8位锁存器&#xff0c;如下所示&#xff1a; 这个锁存器一…

vbscript+asp编写接口

1、前言 因为目前工作在对内网老系统用reactjava微服务进行升级改造&#xff0c;因为一些老的业务逻辑都是用vbscript编写的&#xff0c;很复杂&#xff0c;因此持久层和业务层代码不能动&#xff0c;以asp接口的形式给到数据。java接口调用asp接口&#xff0c;然后前端再调用j…

Elasticsearch --- DSL、RestClient查询文档、搜索结果处理

一、DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 1.1、DSL查询分类 Elasticsearch提供了基于JSON的DSL&#xff08;Domain Specific Language&#xff09;来定义查询。常见的查询类型包括&#xff1a; 查询所有&#xff1a;查询出所有数据&#xff0c…

他工作10年,老板却让他走人

大家好&#xff0c;我是五月&#xff0c;一个编程街溜子。 二狗被裁了&#xff0c;他在公司待了快十年&#xff0c;他想留下来&#xff0c;老板却让他走。 我和他一样困惑。 他985毕业&#xff0c;工作中有从0开始一个项目直到日活过千万&#xff0c;也有过参与顶级产品核心…

【数据结构】算法的时间复杂度和空间复杂度(含代码分析)

文章目录 一、算法的效率1.1 如何衡量一个算法的好坏1.2 算法的复杂度的概念 二、大O的渐进表示法三、时间复杂度2.1 时间复杂度的概念2.2常见时间复杂度计算举例 四、空间复杂度2.1 空间复杂度的概念2.2常见空间复杂度计算举例五、解决问题的思路LeetCode-exercise 总结 一、算…

【Java笔试强训 7】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;Fibona…

Android BuildConfig不生成的解决办法

为了验证一些问题新建了一个demo&#xff0c;其依赖的AGP版本是8.0.0 但是在运行过程中报了一个错误就是找不到BuildConfig。 重新build了下代码&#xff0c;然后找编译后的代码&#xff0c;发现确实没有生成BuildConfig. 给我整的直接怀疑人生&#xff0c;以为是自己的AS有问…

QT、事件处理机制

闹钟 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimer> //定时器 #include <QTime> //shijian #include <QTimerEvent> //定时器事件类 #include <QDateTime> //日期实间类 #include <QTextToSpeech> …