linux入门---动静态库的加载

news2024/11/17 11:34:23

目录标题

  • 为什么会有动态库和静态库
  • 静态库的实现
  • 动态库的实现
  • 动静态库的加载

为什么会有动态库和静态库

我们来模拟一个场景,首先创建两个头文件
在这里插入图片描述
根据文件名便可以得知add.h头文件中存放的是加法函数的声明,sub.h头文件中存放的是减法函数的声明,既然有头文件那么也应该存在对应的源文件,所以这里再创建两个源文件:
在这里插入图片描述
然后就在头文件中添加函数的声明,在源文件中添加函数的实现即可,那么这里的代码如下:

//add.h
#pragma once                                                                                                                                             
#include<stdio.h>                                                                                                                                                                                                       
extern int add(int x ,int y); 
   
//add.c
#include"add.h"                                                                                                                                          
int add(int x ,int y)    
{    
     printf("enter Add func, %d + %d = ?\n", x, y);    
     return x+y;    
}
  
//sub.h
#pragma once                                                                                                                                           
#include<stdio.h>                                  
extern int sub(int x ,int y);    

//sub.c
#include"sub.h"      
int sub(int x ,int y)    
{    
    printf("enter Sub func, %d - %d = ?\n", x, y);    
    return x-y;                                                                                                                         
} 

有了这四个文件之后我们就可以再创建一个文件,并且使用这两个源文件的内容来实现一些功能,那么这里我们就创建一个main.c文件:
在这里插入图片描述
main.c中的代码如下:

#include"add.h"    
#include"sub.h"    
int main()    
{    
    int x =20;    
    int y=10;    
    printf("result: %d\n",add(x,y));    
    printf("result: %d\n",sub(x,y));                                                                                                    
    return 0;                                                       
}  

然后我们就可以使用gcc指令来生成一个名为test的可执行程序,比如说下面的图片:
在这里插入图片描述
运行一下这个可执行程序便会出现下面这样的场景:
在这里插入图片描述
那么这就说明代码的实现是正确的。在之前的学习中我们知道可以用-c选项来生成二进制文件,并且我们还可以使用二进制文件来生成可执行程序,比如说下面的操作:
在这里插入图片描述
并且可执行程序的运行结果也是一摸一样的:
在这里插入图片描述
因为二进制文件的内容正常人是看不懂的,所以我们可以利用这个特性来传播一些保密的资源,比如说有个东西我想让你使用但是不想让你知道这个东西的底层实现逻辑时就可以使用二进制文件来实现:
在这里插入图片描述
是不是看起来很难受对吧,所以这就可以起到一个保密的功能
在这里插入图片描述
假设folder1是功能的发明者folder2是功能的使用者,那么folder2要想使用这个功能就得知道这个功能是如何实现的,所以得将add.o和sub.o文件复制到folder2文件夹里面,然后再将main.c文件赋值到folder2文件夹里面,比如说下面的图片:
在这里插入图片描述
然后我们在folder2文件夹里面想要再生成一个可执行程序时就会爆出这样的错误:
在这里插入图片描述
可以看到这里说没有找到add.h文件,说明给别人方法时不仅得给别人.o文件还得给别人.h文件:
在这里插入图片描述
再来到folder2文件夹里面生成可执行文件时就可以看到成功了:
在这里插入图片描述
并且运行一下可以看到结果跟之前的是一模一样的:
在这里插入图片描述
也就是说未来别人要用我们实现的函数的话我们就可以提供给别人.o文件(方法的实现)和.h文件(都用什么方法),但是这里存在一个问题如果存在很多个.o文件的话这里在传递和使用的时候都非常的麻烦,所以我们就尝试着将所有的.o文件都打一个包形成一个库,这样在传递一个功能和方法的时候给对方一个库文件即可,库文件就是多个.o文件形成的一个文件,而采用不同的工具和方法生成的库就称为静态库和动态库,库的本质就是.o文件的集合,那么接下来我们就来看看如何生成静态库和动态库,并且这两个库会存在什么样的区别。

静态库的实现

使用ar指令来创建静态库,ar的全程是archive,使用方法ar -rc 生成的库文件名 源文件名,这样就可以生成一个静态库,比如说当前路径下的文件如下:
在这里插入图片描述
接下来我们要完成makefile文件里面的内容,静态库的命名规则是lib开头以.a结尾所以库的名称为libmymath.a,库文件依靠add.o文件和sub.o文件,实现的方法是ar -rc libmymath.a add.o sub.o,那么makefile的第一条指令的内容如下:

libmymath.a:add.o sub.o    
    ar -rc $@ $^   

但是当前路径下没有add.o文件和sub.o文件,所以我们还得添加这两个文件的指令和对应的实现方法,那么这里的代码如下:

libmymath.a:add.o sub.o    
    ar -rc $@ $^
add.o:add.c    
    gcc -c  add.c    
sub.o:sub.c    
    gcc -c  sub.c

通过前面的例子我们知道要想传递一个功能不仅得传递.o文件还得传递对应的.h文件,所以这里为了传递方便我们就创建一个文件夹,文件夹中存在两个小文件夹一个名为lib用来存放所有的库文件,一个名为include用来存放所有的头文件,所以这里再添加一个output指令该指令的实现方法就是创建一系列的文件夹,将所有的.o文件放到一个文件夹里面将所有的.h文件放到另外一个文件夹里面,那么这里的代码如下:

libmymath.a:add.o sub.o    
    ar -rc $@ $^
add.o:add.c    
    gcc -c  add.c    
sub.o:sub.c    
    gcc -c  sub.c
.PHONY:output  
output:
    mkdir -p mylib/include
    mkdir -p mylib/lib 
    cp -f *.h mylib/include
    cp -f *.a mylib/lib  

最后就是clean指令,这个指令就删除当前路径下的所有.o文件和库文件就可以了,那么makefile的完整代码如下:

libmymath.a:add.o sub.o    
    ar -rc $@ $^
add.o:add.c    
    gcc -c  add.c    
sub.o:sub.c    
    gcc -c  sub.c
.PHONY:output  
output:
    mkdir -p mylib/include
    mkdir -p mylib/lib 
    cp -f *.h mylib/include
    cp -f *.a mylib/lib 
.PHONY:clean
clean:
    rm -f *.o libmymath.a

输入make指令变可以看到当前路径下出现了几个文件:
在这里插入图片描述

file一下libmymath.a文件便可以看到他说这个文件是一个归档文件,
在这里插入图片描述
然后为了方便讲库文件和头文件的传递,我们还要输入一个make output指令来生成文件夹集合:

然后我们就可以使用tar指令将生成的文件夹进行打包,然后就可以把打包文件放到yum源上,这样别人就可以使用yum来进行下载,或者将这个软件放到某个网站上供别人下载:
在这里插入图片描述

假设下载完成就是将这个打包文件放到上级目录的folder3目录里面:
在这里插入图片描述

那么下载完做的第一件事情就是将打包文件进行解压得到内部的文件:
在这里插入图片描述

然后将这个文件夹里面的头文件都安装到系统的头文件目录里面也就是/usr/include/,将所有的库文件也拷贝到系统的库文件里面也就是/lib64/,那么上面拷贝的过程就是安装,所谓的安装就是将目标文件拷贝到系统指定的路径下,通过这个路径系统可以找到这些文件,那么这时我们有了库文件和头文件按道理来说在folder3文件夹里面就可以使用这些函数来执行一些功能,所以我们就再把main.c文件复制到folder3文件里面然后再使用该文件生成一个可执行程序比如说下面的操作:
在这里插入图片描述

但是生成可执行程序的时候又会出现问题:编译器找不到头文件,我们知道编译器找查找头文件的时候默认在两个地方进行搜索一个当前路径下搜索,一个是在系统指定的路径下进行搜索,我们没有将当头文件和库文件下载到系统路径了里面,所以当前的搜索方式是在当前路径下进行查找,虽然头文件在folder3文件夹里面可是头文件太深了并没有和main.c文件位于同一水平上,所以找不到目标文件那么要想解决这里的问题就得告诉编译器在什么位置下搜索头文件,所以得添加-I选项和指定查找的路径,比如说下面的操作:
在这里插入图片描述
这里依然会报错但是这里报错的原因是链接错误,说明头文件找到了但是库文件没有找到,但是之前敲代码的时候我们并没有告诉编译器库文件在哪里?那为什么也能编译通过呢?因为c和c++的库也是在系统的默认路径下,也就是lib64和/usr/lib下就存放着库文件,所以还得添加-L选项并加上库文件的路径,比如说下面的操作:
在这里插入图片描述
但是运行之后还会报错,因为链接第三方库的时候还得告诉库的名称,因为系统不知道第三方库叫什么,但是之前写代码的时候从来没有指明过库的名称为什么还能正常运行呢?原因很简单并不是你不指明就也能正常通过而是编译器自动帮你填写了,为什么c++的编译器叫做g++c语言的编译器叫做gcc,因为这些编译器知道你写的程序缺少什么库,但是对于第三方的库他就无法得知了,所以添加-l加上库的名字,这里的名字得去掉前缀和后缀,比如说下面的操作:
在这里插入图片描述
生成可执行程序的时候我们还可以发现这里的连接是动态链接并且查看链接库的时候并没有接我们创建的库,因为gcc默认是动态链接,生成一个可执行程序的时候会链接多个库所以这里就会出现问题,gcc默认是动态链接但是这是一个建议过程,对于一个特定的库究竟是动态还是静态库还是取决于你提供的是动态库还是静态链接,如果动静态库都给你了这里的选着权就来到了gcc上面,如果有100个库70个是动态库还有30个是静态库,那么库在链接的时候还是一个一个链接,但是只要有一个库是动态链接的这个可执行程序就是动态链接的。这里在生成可执行程序的时候还是太麻烦了如果想要减少指令的话这里采用的方法就是将头文件都拷贝到/usr/include/里面,再将库文件拷贝到/lib64/里面,那么这种行为就是安装的过程将目标文件拷贝到指定路径下面,但是运行起来还是会报错因为不知道要使用哪个库,所以即便拷贝好了也得告诉程序你使用的是哪个库。

动态库的实现

动态库也是和之前一样先用gcc生成.o结尾的二进制文件,但是这里生成二进制文件的时候得添加一个-fPIC选项,这个选项就是在生成.o文件的时候产生与位置无关码,比如说下面的操作:
在这里插入图片描述
然后就对所有的.o文件进行归档,但是这里的归档和静态库的指令不一样,动态库是用gcc指令加 -shared选项进行归档,比如说下面的操作:
在这里插入图片描述
这样就生成了一个动态库,同样的道理这里再创建一个文件夹,文件夹里面还有两个小文件夹一个用来存放头文件一个用来存放库文件,然后将这个大文件拷贝到folder3文件夹里面,比如说下面的操作:
在这里插入图片描述
在这里插入图片描述

然后也是同样的道理,生成可执行程序的时候会告诉我们找不到头文件:
在这里插入图片描述
所以得添加-I选项来指明具体的路径:
在这里插入图片描述
然后还得告诉编译器库文件在哪里,所以还得添加-L选项:
在这里插入图片描述
光找到库的位置不行还得告诉编译器库的名字叫什么所以这里还得添加-l选项指名库的名称:
在这里插入图片描述
可以看到这里确实生成了可执行程序但是我们运行一下这个程序时便会发现这里依然是有问题的,并且使用ldd指令查看该文件连接库的情况时会发现libmymath.so动态库根本没有连接上去:
在这里插入图片描述

这里的错误表示库没有链接上去没有找到库,但是我已经告诉了库的名称库的位置和头文件了为什么还找不到了,原因很简单你这里的告诉是跟gcc说的,程序在编译链接的时候还和gcc有关吗?没关系了程序在运行的时候需要依靠操作系统和shell,所以操作系统和shell也需要知道库在哪里,操作系统和shell只会去系统路径下进行查找,而我们刚刚写的文件并不在那些路径下所以就找不到,所以就会出现上面这样的问题,那么要想解决这里的问题就得让操作系统和shell找到动态库,那么这里就存在多个方法来解决这里的问题:
方法一:修改环境变量
环境变量LD_LIBRARY_PATH中记录了操作系统默认查找的路径,
在这里插入图片描述

只要我们把库所在的路径导入到该环境变量里面操作系统就会在该路径下搜索库,比如说下面的操作:
在这里插入图片描述
这样库所在的路径就会导入到环境变量里面,并且再运行一下上面的程序便可以发现没有问题了:
在这里插入图片描述
那么这就是方法一修改环境变量,这种方法存在一个问题就是不持久,因为每次登录进入系统都会更新一次环境变量所以该方法是不持久的。

第二种方法:安装
这种方法就是将动态库拷贝到/lib64里面,这样操作系统就可以从系统库中找到我们写的库,但是我们写的库不一定是安全的,所以这里就不展示该方法的实现了,大家也不要尝试这样的方法。

第三种:配置文件法
在系统中存在这么一个路径:/etc/ld.so.conf.d/,这个路径下存在很多的配置文件:
在这里插入图片描述
因为操作系统在查找库的时候会查询一下配置文件也就是.conf结尾的文件,所以我们可以通过创建配置文件的方式来让操作系统找到对应的动态库,那么这里我们就先创建一个.conf结尾的文件,然后将库所在的路径填入到新创建的文件里面:
在这里插入图片描述
然后就强行退出并保存文件,然后使用ldconfig指令更新一下所有的conf文件,这样我们就可以永久的正常的执行刚刚写的程序:
在这里插入图片描述
那么这就是方法三。

第四种方法:软链接
程序在搜索库文件的时候会默认在当前路径下进行搜索,所以我们可以在程序所在的路径下创建一个软连接让其指向动态库文件,比如说下面的操作:
在这里插入图片描述
然后再运行一下该程序便可以发现可以正常的运行:
在这里插入图片描述

动静态库的加载

静态库不考虑加载的过程,在生成可执行程序的时候静态库会把有关代码拷贝直接拷贝进程序里面(用了printf的代码就拷贝库中有关printf的代码),然后程序再加载进内存所以当有多个程序都采用静态链接的时候就会出现冗余的现象,这里存在一个问题将库的代码拷贝到我们的程序里面,那是拷贝到程序中的哪里地方呢?磁盘中的程序含有虚拟地质空间,库中的代码在磁盘上的代码区,所以生成可执行程序的时候是将库中的代码拷贝到程序的代码区,所以程序在查找函数的时候也是去代码区找查找。动态链接是将动态库中的指定函数的地址写入到可执行程序里面,因为printf函数在文件里面也有对应的地址,将该地址写入到可执行程序里面然后程序里面就可以根据该地址找到对应的方法,那这个地址是什么地址呢?我们之前说过一个东西叫做位置无关码那这个又是什么意思呢?我们根据一个生活例子来理解,假设一个马路上只有一个红绿灯,在红绿灯东边100米有一家餐馆,那么我们要想找到这个餐馆是不是只用找到这个马路上的红绿灯就可以了,只要找到了红绿灯就可以往东边走100米从而找到餐馆,那么我们把这样的地址称为相对地址,动态库加载进程序中的地址就是这样的相对地址,动态库的地址由两个地址组成一个start起始地址,一个是该函数在库中相对于库起始地址的偏移地址,start具体是多少在生成程序的时候我们还不知道,当操作系统执行程序发现该程序需要使用库函数的时候就会将该函数对应的动态库加载进内存里面,然后再通过页表将该库映射到虚拟内存中的共享区里面,最后将该库在地址空间上的起始地址填入到start里面,这样程序就可以根据start地址加上每个函数独有的偏移量来找到共享区上的库中的每个函数的内容,那么这就是动态库的加载,而静态库则是相对确定的,当库中的代码被加载进内存的时候库中的代码就已经确定了,可以直接根据地址进行查找,那么这就是动态库的加载。

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

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

相关文章

【每日运维】U盘启动盘安装 ESXi 6.7.0 安装卡在 loading /bnxtroce.v00

问题描述 ● ESXi 6.7.0 安装进度卡在loading /bnxtroce.v00 进度处 处理方法 ● 重新制作启动盘&#xff0c;写入方式改为&#xff1a;【USB-ZIPv2】 ● 设置服务器的 bios设置&#xff0c;启动方式改为【UEFI】 ● 重启开机安装即可

蛋白与蛋白互作预测 蛋白互作预测protein

How to prepare structures for HADDOCK? – Bonvin Labhttps://www.bonvinlab.org/software/bpg/structures/RosettaDock: 蛋白-蛋白复合物对接预测 - 知乎 (zhihu.com) 要进行LPR1-SEPP1复合物的结合亲和力预测&#xff0c;您可以按照以下步骤进行&#xff1a; 获取蛋白质结…

MySQL的Json类型个人用法详解

前言 虽然MySQL很早就添加了Json类型&#xff0c;但是在业务开发过程中还是很少设计带这种类型的表。少不代表没有&#xff0c;当真正要对Json类型进行特定查询&#xff0c;修改&#xff0c;插入和优化等操作时&#xff0c;却感觉一下子想不起那些函数怎么使用。比如把json里的…

vue实现列表自动滚动效果

效果如图&#xff1a; 1.下载插件 npm install vue-seamless-scroll --save 2.在main.js中引入注册 import scroll from vue-seamless-scroll Vue.use(scroll) 3.在页面中使用&#xff08;写一个固定的表头 el-table:show-header"status" 设置为false,自带的表头不…

嵌入式虚拟仿真实验教学平台使用教程之搭建课程计划

嵌入式虚拟仿真实验教学平台使用教程之创建课程计划 所谓「课程计划」就是将一系列实验按照一定的顺序组织成一个教学计划&#xff0c;和传统的教学计划模式比较类似。接下来我将为大家讲解如何通过该平台创建属于自己的课程计划。 嵌入式虚拟仿真实验教学平台提供了两种创建课…

Python综合案例(基本地图使用)

一、基本地图的使用 基本代码&#xff1a; """ 演示地图可视化的基本使用 """ from pyecharts.charts import Map from pyecharts.options import VisualMapOpts# 准备地图对象 map Map() # 准备数据 data [("北京", 99),("…

js获得相对路径文件,并上传到服务器

如何通过js获得相对路径文件 已知一个相对路径文件&#xff0c;如何使用js将该文件读取为File格式&#xff0c;最后上传到服务器中呢。 1.最简单的解决方案——fetch 代码 import ./index.scss// js通过相对路径获取文件 function FetchGetLocalFile() {const fetchLocalFile …

【0904作业】QT 完成登陆界面跳转到聊天室+完成学生管理系统的查找和删除功能

一、完成登陆界面跳转到聊天室 1> 项目结构 2> 源码 ① .pro ②main #include "mywnd.h" #include"chatCli.h" #include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);MyWnd w;w.show();Form f;QObject::co…

【LeetCode算法系列题解】第51~55题

CONTENTS LeetCode 51. N 皇后&#xff08;困难&#xff09;LeetCode 52. N 皇后 II&#xff08;困难&#xff09;LeetCode 53. 最大子序和&#xff08;中等&#xff09;LeetCode 54. 螺旋矩阵&#xff08;中等&#xff09;LeetCode 55. 跳跃游戏&#xff08;中等&#xff09; …

【深入解析spring cloud gateway】07 自定义异常返回报文

Servlet的HttpResponse对象&#xff0c;返回响应报文&#xff0c;一般是这么写的&#xff0c;通过输出流直接就可以将返回报文输出。 OutputStream out response.getOutputStream(); out.write("输出的内容"); out.flush();在filter中如果发生异常&#xff08;例如…

如何使用GPT引领前沿与应用突破之GPT4科研实践技术与AI绘图

GPT对于每个科研人员已经成为不可或缺的辅助工具&#xff0c;不同的研究领域和项目具有不同的需求。例如在科研编程、绘图领域&#xff1a; 1、编程建议和示例代码: 无论你使用的编程语言是Python、R、MATLAB还是其他语言&#xff0c;都可以为你提供相关的代码示例。 2、数据可…

TypeScript_树结构-BST树

树结构 树的特点 树通常有一个根。连接着根的是树干树干到上面之后会进行分叉成树枝&#xff0c;树枝还会分又成更小的树枝在树枝的最后是叶子 树的抽象 树可以模拟生活中的很多场景&#xff0c;比如&#xff1a;公司组织架构、家谱、DOM Tree、电脑文件夹架构 优秀的哈希函…

神策数据 CJO 系列丨解密 CJO:连接体验的下一个前沿趋势

10 余年前&#xff0c;市场营销的焦点聚集在增长黑客如何利用 AARRR 模型&#xff08;获取 Acquisition、激活 Activation、留存 Retention、收入 Revenue、传播 Referral&#xff09;来推动并加速企业的生长发展。我们曾相信&#xff0c;在 AARRR 漏斗中&#xff0c;只要我们吸…

宝塔面板定时监控和重启MySQL数据库(计划任务)

往期教程 如果还有不了解宝塔面板怎么使用的小伙伴&#xff0c;可以看下我总结的系列教程&#xff0c;保证从新手变老鸟&#xff1a; 【建站流程科普】 个人和企业搭建网站基本流程及六个主要步骤常见的VPS主机运维面板汇总—网站运维面板云服务器&#xff0c;VPS&#xff0…

76 # koa 上下文的实现原理

上一节实现了 koa 基本逻辑实现以及属性的扩展&#xff0c;下面继续实现上下文的实现 ctx 跟 proto 的关系 ctx.__proto__.__proto__ protoMDN&#xff1a;defineGetter 备注&#xff1a; 此特性已弃用&#xff0c;建议使用对象初始化语法或 Object.defineProperty() API 来…

《职场情绪稳定:内在的力量与策略》

近期发生的新闻热点&#xff0c;如大规模裁员、创业公司倒闭、公共卫生事件等&#xff0c;让公众更加关注稳定情绪和心理健康的问题。在职场中&#xff0c;我们常常遇到各种挑战和压力&#xff0c;如何保持稳定的情绪成了一个重要的话题。 首先&#xff0c;让我们分享一些工作中…

RecyclerView的smooth scroller -- 诸多案例

作者&#xff1a;snwrking 最近碰到好几个使用LinearSmoothScroll(下方简称为LSS)的场景, 让我对这个类的了解更加进一步, 所以分享在这, 希望对有需要的同学有所帮助. 我个人不太喜欢太理论的东西, 所以整篇文章几乎全是我做过的案例, 也方便也有类似需求的同学对号入座地取用…

【用unity实现100个游戏之8】用Unity制作一个炸弹人游戏

文章目录 前言素材开始一、绘制地图二、玩家设置三、玩家移动四、玩家四方向动画运动切换 五、放置炸弹六、生成爆炸效果七、墙壁和可破坏障碍物的判断八、道具生成和效果九、玩家死亡十、简单的敌人AI十一、虚拟摇杆 待续源码完结 前言 我们将在这个视频中&#xff0c;学习如…

Oracle 遍历变量游标

背景 由于我们的数据库系统中的游标特别多&#xff0c;DBA让我们优化&#xff0c;减少游标的使用。 电脑系统&#xff1a;windows数据库&#xff1a;Oracle数据库图形化界面工具&#xff1a;Toad&#xff0c;DBeaver(我測試的時候用的)记录日期&#xff1a;2023-09-04 具体实…

macbookpro怎么删除软件没有鼠标

macbookpro怎么删除软件没有鼠标,macbookpro触摸板可以替代鼠标进行操作。左右键功能与鼠标相同&#xff0c;可用于执行删除操作。此外&#xff0c;还可以利用键盘上的Delete键来删除选中的文件。 删除软件方法 方法1、打开应用程序&#xff0c;键盘按住control&#xff0c;加点…