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

目录标题

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

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

我们来模拟一个场景,首先创建两个头文件
在这里插入图片描述
根据文件名便可以得知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/includemkdir -p mylib/lib cp -f *.h mylib/includecp -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/includemkdir -p mylib/lib cp -f *.h mylib/includecp -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.rhkb.cn/news/121670.html

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

相关文章

算法面试-深度学习基础面试题整理(2023.8.29开始,每天下午持续更新....)

一、无监督相关&#xff08;聚类、异常检测&#xff09; 1、常见的距离度量方法有哪些&#xff1f;写一下距离计算公式。 1&#xff09;连续数据的距离计算&#xff1a; 闵可夫斯基距离家族&#xff1a; 当p 1时&#xff0c;为曼哈顿距离&#xff1b;p 2时&#xff0c;为欧…

[MySQL]查看数据库大小

查看库大小 例如&#xff1a;查看当前MySQL中数据总量超过2GB的库&#xff1a; select table_schema as 数据库,table_rows as 记录数,data_size as 数据容量(GB),index_size as 索引容量(MB) from (selecttable_schema,sum(table_rows) as table_rows,sum(truncate(data_leng…

Qt中布局管理使用总结

目录 1. 五大布局 1.1 QVBoxLayout垂直布局 1.2 QHBoxLayout水平布局 1.3 QGridLayout网格布局 1.4 QFormLayout表单布局 1.5 QStackedLayout分组布局 1.6 五大布局综合应用 2. 分割窗口 3. 滚动区域 4. 停靠区域 1. 五大布局 1.1 QVBoxLayout垂直布局 #include <…

Sentinel 流量控制框架

1. Sentinel 是什么&#xff1f; Sentinel是由阿里中间件团队开源的&#xff0c;面向分布式服务架构的轻量级高可用流量控制组件。 2. 主要优势和特性 轻量级&#xff0c;核心库无多余依赖&#xff0c;性能损耗小。 方便接入&#xff0c;开源生态广泛。 丰富的流量控制场景。 …

图神经网络和分子表征:4. PAINN

如果说 SchNet 带来了【3D】的火种&#xff0c;DimeNet 燃起了【几何】的火苗&#xff0c;那么 PAINN 则以星火燎原之势跨入 【等变】时代。 在 上一节 中&#xff0c;我们提到&#xff0c; PAINN 在看到 DimeNet 取得的成就之后&#xff0c;从另一个角度解决了三体几何问题&a…

『PyQt5-Qt Designer篇』| 08 Qt Designer中容器布局和绝对布局的使用

08 Qt Designer中容器布局和绝对布局的使用 1 容器布局1.1 设计容器布局1.2 保存文件并执行2 绝对布局2.1 设计绝对布局2.2 保存文件并执行1 容器布局 1.1 设计容器布局 先拖入一个容器Frame容器,然后拖入几个控件: 把拖入的控件拖入容器中: 选中容器,右键-布局-栅格布局:…

Node基础and包管理工具

Node基础 fs 模块 fs 全称为 file system&#xff0c;称之为 文件系统&#xff0c;是 Node.js 中的 内置模块&#xff0c;可以对计算机中的磁盘进行操作。 本章节会介绍如下几个操作&#xff1a; 1. 文件写入 2. 文件读取 3. 文件移动与重命名 4. 文件删除 5. 文件夹操作 6. …

mysql索引为什么提高查询速度(底层原理)

一、索引原理图 二、索引数据存储到硬盘而不是内存&#xff1f; 硬盘内存 成本低成本高 容量大容量小 读写速度一般读取速度快 断电后数据永久存储断电后数据清空 三、硬盘数据为什么要读取到内存&#xff1f;为啥不直接…

【狂神】Spring5笔记(四)之Mybatis和事物的整合

一、整合Mybatis方式一 目录结构&#xff1a; 大致内容结构&#xff1a; 主要难点就在于applicationContext.xml中相关配置的理解 代码图片如下 这个类就专门用于对象的创建就可以了 测试类&#xff1a; 实现类&#xff1a; SqlSessionTemplate 二、整合Mybatis方式二 相关代码…

Go在安装Gin时出现Failed to connect 报错问题的解决方案(已解决)

在命令行中输入&#xff1a;go get -u github.com/gin-gonic/gin指令安装Gin第三方包时出现连接错误与连接超时的情况如下&#xff1a; 在较新版本的Go中引入了全新的包管理机制&#xff0c;出现上述错误可能是包管理机制设置不恰当的问题&#xff0c;尝试在终端窗口输入如下…

9-AJAX-3原理

AJAX-原理 目录 XMLHttpRequest 的学习Promise封装简易版 axios案例 - 天气预报 学习目标 了解原生 AJAX 语法 - XMLHttpRequest&#xff08;XHR&#xff09;了解 Promise 的概念和使用了解 axios 内部工作的大概过程&#xff08;XHR Promise&#xff09;案例 - 天气预报 …

DHCP工作过程详解

只有是一个网段的&#xff0c;它才会发送 ARP 请求&#xff0c;获取 MAC 地址。如果发现不是呢&#xff1f;Linux 默认的逻辑是&#xff0c;如果这是一个跨网段的调用&#xff0c;它便不会直接将包发送到网络上&#xff0c;而是企图将包发送到网关。 因为网关要和当前的网络至…

Vue3响应式源码实现

Vue3响应式源码实现 初始化项目结构 vue-proxy ├── effect.js ├── effect.ts ├── index.html ├── index.js ├── package.json ├── reactive.js ├── reactive.ts └── webpack.config.jsreactive.ts import { track, trigger } from "./effect&q…

leetcode 第 361 场周赛

2843. 统计对称整数的数目 核心思想&#xff1a;枚举每一个数是否是对称整数,第一种写法为python写法&#xff0c;第二种为一般写法我觉得更好&#xff0c;非常有思想性。 2844. 生成特殊数字的最少操作 核心思想&#xff1a;枚举特殊数字结尾的几种可能。其实自己做的时候一…

k8s环境部署配置

目录 一.虚拟机准备 二.基础环境配置&#xff08;各个节点都做&#xff09; 1.IP和hosts解析 2.防火墙和selinux 3.安装基本软件 4.配置时间同步 5.禁用swap分区 6.修改内核参数并重载 7.配置ipvs 三.docker环境&#xff08;各个节点都做&#xff09; 1.配置软件源并…

高数刷题笔记

在标准解答的时候可以用极限趋向于0去证明。上上图 性质解答则可以直接略去高阶&#xff0c;只看低阶。 先用等价无穷小将sin x换为x再看&#xff01; 反函数此题可以用洛必达&#xff0c;也可以像这样去换自变量。 1-cosx2sin^2(x/2)x^2/2; 圈2表示了sinx ,x,tan x之间的差距…

MyBatis关系映射

文章目录 前言一、一对一映射1.1 创建实体1.2 xml配置 二、一对多映射2.1 创建实体2.2 resultMap配置2.3 测试 三、 多对多映射3.1 创建实体3.2 resultMap配置3.3 测试 前言 MyBatis是一个Java持久化框架&#xff0c;它提供了一种将数据库表和Java对象之间进行关系映射的方式。…

神经网络NLP基础 循环神经网络 LSTM

用的时候&#xff0c;只关心token的输入&#xff0c;以及hidden state就好了 sequence的length是多少&#xff0c;lstm的cell的数量就是多少 LSTM BI-LSTM stacked lstm GRU 实现

【SpringBoot应用篇】SpringBoot集成MinIO对象存储服务

【SpringBoot应用篇】SpringBoot集成MinIO对象存储服务 对象存储服务MinIOMinIO简介MinIO特点开箱使用docker安装启动管理控制台 快速入门Java 上传文件到minio配置访问权限 封装MinIO为starter创建模块zy-minio-starter配置类封装操作minIO类对外加入自动配置其他微服务使用 安…

海域可视化监管:浅析海域动态远程视频智能监管平台的构建方案

一、方案背景 随着科技的不断进步&#xff0c;智慧海域管理平台已经成为海洋领域监管的一种重要工具。相比传统的视频监控方式&#xff0c;智慧海域管理平台通过建设近岸海域视频监控网、海洋环境监测网和海上目标探测网络等&#xff0c;可实现海洋管理的数字化转型。 传统的…