Linux:理解动静态库

一、前言

如果我们写了一些方法想给别人用??有什么办法呢??

——>(1)我直接把头文件和源文件给他(.c+.h)    ——>这样会让别人轻易看到你的实现

       (2)把源文件打包成库,再和头文件一起给他(库+.h)——>这样别人看不到你的实现

——>所以平时为了能够不让别人轻易窃取我们的劳动成果,我们一般采用的都是第二种方法,所以这就涉及到了如何把源文件打包成库的问题——>库又分静态库和动态库

注:头文件是必须公开的!!相当于给别人的一份方法使用说明书 

    所以为了学习如何创建静态库和动态库以及理解静态链接和动态链接的本质。我们得从以下两个角度来理解:

(1)站在库的制作者角度——>尝试自己写一个简单的库

(2)站在库的使用者角度——>学会如何使用第三方库 

二、静态链接

静态库 ——libXXX.a  

2.1 静态库的原理和命令

静态库的原理是什么呢??

     反正我如果给你源代码,你也是要先把所有的.c文件以及自己的main.c文件都变成.o才能形成可执行程序,那么我干脆先把这些库文件都变成.o文件,然后顺便帮你打个包,这样你的程序一样可以运行,并且你也看不到我的源代码。

——>静态库的原理:把方法有关的所有源文件都变成.o文件,然后打包成libXXX.a ,然后当main.c变成main.o的时候,就自动跟他一起连接形成一个可执行程序了!!

1、ar是一个生成静态库的命令,第一个是打算生成的.a文件 后面跟着的是所有的.o文件 

2、选项-rc(replace and create)的意思是如果目标静态库文件存在就替换,不存在就创建

2.2 静态库制作和公开

 这样就可以完成静态库的制作,然后可以公开出去,变成一个lib文件夹给用户

2.3 库使用及路径问题

      可是我们在头文件将路径都表示出来,显然不符合我们的使用习惯,如果我们去掉路径只保留mymath.h呢?? 

问题1 :为什么会找不到这个文件呢??

——> (1)没有路径,默认只会在这个路径找

          (2)也会在当前目录找一找 

 ——>解决方案:

(1)直接在头文件里带绝对路径(但是不符合我们的使用习惯)

(2) gcc有一个-I选项,就是告诉gcc,如果你在默认路径和当前路径找不到,你就到我指定的这个目录去找!!

 ——>更倾向于用第二种,因为第二种使用gcc的选项可以对gcc更为了解,不能总是系统怎样你就怎样,要真正学好动静态库,你就要学会去摒弃系统的默认动作,因为只有这样你才能知道编译器有一个查找头文件的动作,而你知道了这个选项就可以尝试去控制这个动作!!

 

可是又报错了,原因是链接报错,因为gcc只能在系统默认路径和当前路径下去找这个库

 ——>解决方案:-L选项,告诉gcc,你如果默认路径和当前路径找不到,你就去我指定的这个目录里去找库

 ——>必须用-l显示告诉gcc要链接哪个库 却一般建议l之后紧跟库名称,因为有些时候可能不止链接一个库!!

 问题2:为什么-I的时候不用指明哪个头文件,但是-L的时候却要指明哪个库呢??

——>因为头文件的名称你已经在源文件里include了,我知道了文件名,你只需要告诉我路径我肯定能够找到,但是你并没有在源文件里告诉我要链接哪个库啊,我就算知道路径了又怎么样?我连他是谁的都不认识。所以你必须要显式地告诉我要链接哪个库!

问题3:怎么以前都不需要带选项,现在使用了第三方库就这么麻烦??

——>之前用不到是因为g++默认就能找到对应C++、C的一些库,但是你用的是一些第三方的库,就必须得这样做才可以!! 

 问题4:有什么其他解决方案吗??

——>-I和-L本质上都是gcc只能在默认路径和当前路径下找,所以我们可以把第三方文件和第三方库都编到系统的路径地下(不一定要拷贝过去,也可以放软连接),这样我们只需要-l告诉gcc要链接哪个库就可以了!——>所以第三方库使用的时候无论如何必须用-l 

2.4 errno的理解

2.5 理解库的安装 

其实我们将库拷贝到系统路径的过程就是——库的安装!! 

       比如我们再下载VS的时候里面就默认会有一些脚本语言,执行一些命令把相关需要的库的文件拷贝到系统的特定路径下,编译器可以找到,但是不建议第三方库这样做,因为可能会污染别人的库

      也可以搞软连接

三、动态链接

3.1 动态库的原理和命令

动态库的原理和静态库一样,因为最后都要链接,所以都是先把-c变成-o,然后再用命令打包起来

和静态库的区别:

(1)gcc编译多了一个选项 -fPIC 

(2)动态库的形成不需要用ar 因为他是gcc的亲儿子,默认就有内置的选项可以去形成,直接带-share选项就是告诉gcc:我不要生成可执行程序,我要生成共享库(前提是这些文件里面没有main函数!!)

3.2 尝试动静态库分离

 问题1:x不是可执行权限吗??为什么动态库文件有x选项,而静态库文件没有x选项??

 ——>因为动态库需要我们在执行的时候跳转过去而静态库没有-x是因为他的做优就是提供一个源代码拷贝过去,当拷贝完成后,你这个程序怎么样我并不关心所以x选项的本质意思是当前的文件是否会以可执行程序的形式加载到内存中,只不过他没有main函数,而是只有方法,无法独立执行,需要依赖别人的调用!!

3.3 系统也得知道动态库在哪

问题1:为什么明明形成了a.out 却还是出现这种情况呢?

——>因为你确实告诉了编译器动态库在那,他也帮你生成了可执行程序,而当你变成可执行程序之后,就和编译器一点关系也没有了,而你的可执行程序运行不了,是因为你也得告诉系统(加载器)你的动态库在哪!

问题2:那为什么系统找得到C库却找不到我们的第三方库呢??

——>因为不仅仅是编译,加载也需要提供路径!!系统不是神,不是你随便说个文件就可以链接,并且不同目录下可能还会有同名文件,C库可能早就被内置好或者是硬编码进系统的,所以系统找得到,但是其他的一些库,你必须想办法让系统找到。 

3.4 解决加载找不到动态库的方法

1、拷贝到系统默认的库路径/usr/lib64

2、在系统的默认库路径/usr/lib64 建立软连接

3、将自己库所在的路径,添加到环境变量LD_LIBRARY_PATH(搜索用户自定义库路径)中

 但是你关闭shell之后就会失效,所以你想要长久拥有的话,就得把环境变量写到系统启动时的配置文件脚本里面

4、/etc/ld.so.conf.d 建立自己的动态库路径的配置文件,然后ldconfig一下

——> 实际上我们用的库都是别人成熟的库,基本上都是使用安装到系统的方式!!

3.5  ncurses

基于终端的图形库界面

3.6 一些我的思考

1、其实一个语言你会用了,语言就不重要了,你更渴望去理解软件的周边知识,就是有很多东西你在用但是你并不懂为什么,所以如果你能懂得为什么,当你再次去使用这个工具的时候,你就会特别清楚,这就是懂得底层原理所带给我们的自信

2、穷则思变,努力不一定成功,但是不谈场景的话都是耍流氓,这句话在普世规律里是对的,而且也是废话,如果我们选择的是一个上升的行业(即使当前经济下行,计算机也是矮个里面挑大个)那么一切可能都会很大。

3、懂业务的程序员才是最重要的程序员(成熟的,可以拿到台面上的那些技术并不值钱,但是能够去研究那些不成熟的技术,有发展趋势的技术才值钱),所以技术和知识是你的一个最底层、融会贯通的能力!!

4、场景越多你对环境变量的理解越深刻,环境变量是系统级别的全局变量,用来支撑编译器、连接器、加载器…… 帮助开发工具搜索他所需要的头文件、源文件、动态库!

5、以前我们写的代码的库是动态库,只不过无论是在windows还是linux,写C、C++相关头文件和库,编译器和系统都可以找到,所以你才能实现无障碍编程,所以你想让第三方库也实现无障碍编程,关键在于如何如何让编译器和系统找到这个库 。

四、动态加载

4.1 动态库加载的底层原理

 1、 当cpu执行代码正文部分的时候,当发现需要被调用的库函数,就会跳到共享区去查找,如果此时库文件还没有被加载进内存,就发生缺页中断,然后将动态库文件加载进来,建立和页表的映射关系,从此往后我们执行的任何代码,都是在我们的进程地址空间中进行!

 2、系统运行时 ,一个进程可能会链接多个库,所以OS必然要把这些库管理起来——>系统中所有库的加载情况,OS非常清楚

4.2 进程地址空间

4.2.1 程序没有加载前的地址

        编译后的天然就给代码编址(逻辑地址)了(比如多态的虚函数表,call某个函数)——>说明此时编译器已经在帮操作系统考虑加载和执行的问题了!! 

        在很早以前没有地址空间概念的时候,编址的逻辑就是段+偏移量,但现如今都变成平坦模式了(严格遵照地址空间来编址0->4GB)

4.2.2 程序加载后的地址(进程)

 

问题1:如何执行第一条指令(main函数头)呢??

——>编译形成可执行程序的时候,会有一张表,存储的是各个段的地址,而表头的地址就是main函数的地址,cpu会拿到之后开始从正文部分执行

问题2:CPU会读取什么??

——>CPU内部读取到的指令,可能是数据,也可能是虚拟地址!!CPU在被设置的时候其实内部就做了很多能够认识这些基础指令的工作(其实就是把一些二进制汇编->一些指令级的东西->结合起来去完成我们要求他完成的工作)

问题3:为什么反汇编后显示出来的地址是不一样的??

 ——>因为每个指令的长度是不一样的!!

4.2.3 总结 

        编译后的可执行程序必须变成进程,然后才能加载到内存中执行。一开动态库文件内容不一定会被加载进来(因为可能很大),而是先创建相关的结构体和地址空间。

        编译后的可执行程序有一个表,表头是入口地址(也是虚拟地址),cpu拿到入口地址后开始执行

     当他检测到虚拟地址在页表中没有映射关系的时候,就会发生缺页中断,将需要的内容加载进内存,然后在页表中建立虚拟地址和物理地址的映射关系

4.3 动态库的地址

         转成汇编后printf已经变成了地址,所以我们的cpu在执行的时候只认识编译时确定好的线性地址,所以我们必须需要保证我们的动态库确实已经加载到这个虚拟地址了,要不然就会找不到(也就是说缺页中断的时候必须把它加载到固定地址处)

——>可是我可能有十个八个库,我怎么保证每个库都恰好被加载到内存中的固定位置呢?我怎么保证哪个库先加载呢???因为这个位置可是在编译的时候就硬编码了啊,所以这是不可能做到的!

——>所以我们就要想办法让库在虚拟内存的任意位置都可以加载

——>解决方法就是采用相对编址的方式,意思就是你可以随便加载,你要你在你的库秒速的结构体里面把加载进去的起始地址给我,然后我就会用起始地址+偏移量的方法找到我想要调用的库函数。

——>还有一个问题就是:我必须得告诉编译器在分配地址的时候,让自己内部的函数不要采用绝对编址,只表示每个函数在库中的偏移量即可!

——>这就是为什么gcc选项需要有有-fPIC的原因,他就是在告诉编译器直接采用偏移量对库中的函数进行编址。

 

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

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

相关文章

快速了解SpringBoot 统一功能处理

拦截器 什么是拦截器: 拦截器是Spring框架提供的重要功能之一,主要进行拦截用户请求,在指定方法前后,根据业务需求,执行预先设定的代码。 也就是说,允许开发⼈员提前预定义⼀些逻辑,在⽤⼾的请求响应前后执⾏.也可以…

随着最新的补丁更新,Windows 再次变得容易受到攻击

SafeBreach专家Alon Leviev发布了一款名为 Windows Downdate的工具,可用于对Windows 10、Windows 11 和 Windows Server 版本进行降级攻击。 这种攻击允许利用已经修补的漏洞,因为操作系统再次容易受到旧错误的影响。 Windows Downdate 是一个开源Pyth…

Linux笔记-对Linux环境变量的进一步认识(2024-08-09)

此篇公开到互联网上的时间是:2024-11-11 主要是PATH和LD_LIBRARY_PATH。 基本概念 在 Linux 中,PATH 和 LD_LIBRARY_PATH 是两个不同的环境变量,它们的作用和使用场景有所不同。 PATH 作用:用来指定可执行文件的搜索路径。当你…

Linux操作系统之DHCP服务部署与配置

一、实验目的 1、理解DHCP的定义和工作原理; 2、掌握DHCP服务器的配置方法。 二、实验环境 1台PC、VMware虚拟机、3个CentOS7操作系统 三、实验步骤及内容 1、使用yum安装dhcp; 图1 安装DHCP服务 图2 查看DHCP是否安装 2、修改VM中虚拟网络编辑器…

优化时钟网络之时钟抖动

Note:文章内容以Xilinx 7系列FPGA进行讲解 1、什么是时钟抖动 时钟抖动就是时钟周期之间出现的偏差。比如一个时钟周期为10ns的时钟,理想情况下,其上升沿会出现在0ns,10ns,20ns时刻,假设某个上升沿出现的时…

第三十六章 Vue之路由重定向/404页面设置/路径模式设置

目录 一、路由重定向 1.1. 使用方式 1.2. 完整代码 1.2.1. main.js 1.2.2. App.vue 1.2.3. index.js 1.2.4. Search.vue 1.2.5. Home.vue 1.3. 运行效果 二、设定404错误页面 2.1. 使用方式 2.2. 完整代码 2.2.1. index.js 2.2.2. NotFound.vue 2.2.3. 运行效…

PostgreSQL的奥秘:深入探究事务与锁的秘密世界

PostgreSQL事务 1. 概述 在数据库系统中,事务(Transaction)是执行数据库操作的最小逻辑单位。它确保了一组操作的完整性和一致性。事务可以通过显式的 BEGIN、COMMIT 和 ROLLBACK 语句块来控制,也可以在自动提交模式&#xff08…

Three.js 搭建3D隧道监测

Three.js 搭建3D隧道监测 Three.js 基础元素场景scene相机carema网络模型Mesh光源light渲染器renderer控制器controls 实现3d隧道监测基础实现道路实现隧道实现多个摄像头点击模型进行属性操作实现点击模型发光效果 性能监视器stats引入使用 总结完整代码 我们将通过three.js技…

netstat中sendq/recvq用于排查发送端发送数据的问题

web同事开发了一个用于接收syslog数据的服务器,不清楚web的开发方式,用来联调的发送端是我们的C模块 反馈syslog udp形式接收正常,速度正常,数量也正常,syslog tcp形式接收开始比较快后面越来越慢,并且知道…

基于python主观题自动阅卷系统毕业设计项目

基于python主观题自动阅卷系统毕业设计项目 大家好,我是陈辰学长,一名在 Java 圈辛勤劳作的码农。今日,要和大家分享的是一款基于python主观题自动阅卷系统毕业设计。项目源码以及部署相关事宜,请联系陈辰学长,文末会…

SparkSql读取数据的方式

一、读取普通文件 方式一:给定读取数据源的类型和地址 spark.read.format("json").load(path) spark.read.format("csv").load(path) spark.read.format("parquet").load(path) 方式二:直接调用对应数据源类型的方法 …

LSTM+LightGBM+Catboost的stacking融合模型

基本介绍 针对目前大部分数据同时具有特征连续和特征不连续的特点,将神经网络模型如LSTM和回归树模型如XGboost,基于stacking集成学习原理进行融合 附有模型评价指标R2、RMSE、MAE、MSE,代码包含注释,可以直接运行。 融合过程 在机器学习中…

重学 Android 自定义 View 系列:动手实现专属 TextView

前言 前面一篇介绍了自定义View的基础概念(皮毛),接下来全部是自定义View实战,让我们一起开启自定义View之旅吧! 1. 实现目标 本篇将实现一个自定义的TextView,通过自定义属性让我们可以配置文本内容、颜色、字体大小。主要是掌…

多用户商城系统的功能及设计和开发

多用户商城系统的功能及设计与开发(基于 PHP MySQL) 在现代电子商务平台的开发中,PHP MySQL 是一对非常流行且高效的技术栈。PHP作为服务器端脚本语言,结合MySQL数据库,可以高效地处理多用户商城系统的各种需求。本…

丹摩征文活动|快速上手 CogVideoX-2b:智谱清影 6 秒视频生成部署教程

文章目录 一、生成视频效果 二、CogVideoX 技术新起点三、CogVideoX 上手部署3.1 创建丹摩实例3.2 配置环境和依赖3.3 模型与配置文件3.4 运行3.5 问题与处理方法 四、CogVideoX-2b 用创新点燃未来 一、生成视频效果 A street artist, clad in a worn-out denim jacket and a c…

实现 think/queue 日志分离

当我们使用think/queue包含了比较多的不同队列,日志会写到runtime/log目录下,合并写入的,不好排查问题,我们遇到一个比较严重的就是用了不同用户来执行,权限冲突了,导致部分队列执行不了. 为了解决以上问题,本来希望通过Log::init设置不同日志路径的,但是本地测试没生效,于是用…

Ubuntu24.04安装Perforce服务

安装 参考链接:https://www.perforce.com/manuals/p4sag/Content/P4SAG/install.linux.packages.install.html Perforce是一款收费的版本控制管理工具,当然其中也有一些免费的教学版本,应需要下载。 下载网址: https://www.perforce.com/downloads/helix-core-p4d安装前…

使用 GitHub Actions 部署到开发服务器的详细指南

使用 GitHub Actions 部署到开发服务器的详细指南 在本篇博客中,我们将介绍如何使用 GitHub Actions 实现自动化部署,将代码从 GitHub 仓库的 dev 分支自动部署到开发服务器。通过这种方式,可以确保每次在 dev 分支推送代码时,服…

Logrus入门

Logrus入门 1. 下载 go get github.com/sirupsen/logrus2. logrus常用方法 logrus.Debugln("Debugln") logrus.Infoln("Infoln") logrus.Warnln("Warnln") logrus.Errorln("Errorln") logrus.Println("Println")// 输出如…

告别重启大法,CPU飙高问题如何排查详细教程以及解决方案

文章目录 0 前言1.确定问题进程2.获取线程信息3.转换线程ID为十六进制4.获取线程堆栈5.分析代码6.性能分析工具7. 查看GC日志8.检查系统资源总结 0 前言 本篇是本人认为最实用的一篇,在日常开发运维工作中,经常遇到CPU较高的情况,一开始时还不…