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.mzph.cn/news/68951.shtml

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

相关文章

文章预览 安防监控/视频存储/视频汇聚平台EasyCVR播放优化小tips

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;可实现视频监控直播、视频轮播、视频录像、云存储、回放与检索、智能告警、服务器集群、语音对讲、云台控制、电子地图、H.265自动转码H.264、平台级联等。为了便于用户二次开发、调用与集成&#xff0c;…

25 Linux可视化-Webmin和bt运维工具

25 Linux可视化-Webmin和bt运维工具 文章目录 25 Linux可视化-Webmin和bt运维工具25.1 Web运行环境简介25.2 Webmin的安装及使用25.2.1 安装webmin25.2.2 Webmin使用演示 25.3 bt(宝塔)的安装及使用25.3.1 安装宝塔25.3.2 宝塔Web登录Linux服务器25.3.3 找回宝塔登录密码 学习视…

Chrome 和 Edge 上出现“status_breakpoint”错误解决办法

文章目录 STATUS_BREAKPOINTSTATUS_BREAKPOINT报错解决办法Chrome浏览器 Status_breakpoint 错误修复- 将 Chrome 浏览器更新到最新版本- 卸载不再使用的扩展程序和应用程序- 安装计算机上可用的任何更新&#xff0c;尤其是 Windows 10- 重启你的电脑。 Edge浏览器 Status_brea…

iOS实时监控与报警器

在现代信息化社会中&#xff0c;即使我们不在电脑前面也能随时获取到最新的数据。而苹果公司提供的iOS推送通知功能为我们带来了一种全新的方式——通过手机接收实时监控和报警信息。 首先让我们了解一下iOS推送通知。它是一个强大且灵活可定制化程度高、适用于各类应用场景&a…

基于沙猫群算法优化的BP神经网络(预测应用) - 附代码

基于沙猫群算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于沙猫群算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.沙猫群优化BP神经网络2.1 BP神经网络参数设置2.2 沙猫群算法应用 4.测试结果&#xff1a;5.Matlab代…

论文研读|生成式跨模态隐写发展综述

前言&#xff1a;本文介绍近5年来生成式跨模态隐写领域的相关工作。 相关阅读&#xff1a;生成式文本隐写发展综述 不同于文本隐写&#xff0c;跨模态隐写需要考虑不同模态间的相关性&#xff0c;常见的跨模态场景有&#xff1a;Image-to-Text&#xff08;如图像描述&#xff…

【Python】OpenCV安装

安装起来相当简单&#xff0c;但是看到很多博客的安装过程复杂得很。 pip install opencv-python测试代码&#xff1a; import cv2 as cv img cv.imread("f6759b83f3201997fd7ea1c9b9130a44.jpg")cv.imshow("Display window", img) k cv.waitKey(0) # …

ROS 2官方文档(基于humble版本)学习笔记(二)

ROS 2官方文档&#xff08;基于humble版本&#xff09;学习笔记&#xff08;二&#xff09; 理解节点&#xff08;node&#xff09;ros2 runros2 node list重映射&#xff08;remap&#xff09;ros2 node info 理解话题&#xff08;topic&#xff09;rqt_graphros2 topic listr…

【2023年11月第四版教材】第10章《进度管理》(第三部分)

第10章《进度管理》&#xff08;第三部分&#xff09; 7 估算活动持续时间7.1 估算活动持续时间7.2 类比估算★★★7.3 参数估算★★★7.4 三点估算★★★7.5 数据分析★★★ 7 估算活动持续时间 组过程输入工具和技术输出计划4,估算活动持续时间1.项目管理计划&#xff08;进…

数据库基础

目录 一、数据库是什么&#xff1f; 二、目前主流的数据库 三、数据库基本使用 1.连接服务器 2.服务器、数据库、表关系 3.使用案例 4、数据逻辑存储 四、MySQL基本知识 1、MySQL架构 2、SQL语句分类 3、存储引擎 总结 一、数据库是什么&#xff1f; 数据库是按照数据结构来组…

golang读取键盘功能按键输入

golang读取键盘功能按键输入 需求 最近业务上需要做一个终端工具&#xff0c;能够直接连到docker容器中进行交互。 技术选型 docker官方提供了python sdk、go sdk和remote api。 https://docs.docker.com/engine/api/sdk/ 因为我们需要提供命令行工具&#xff0c;因此采用g…

6、NoSQL的四大分类

6、NoSQL的四大分类 kv键值对 不同公司不同的实现 新浪&#xff1a;Redis美团&#xff1a;RedisTair阿里、百度&#xff1a;Redismemcache 文档型数据库&#xff08;bson格式和json一样&#xff09; MongoDB MongoDB是一个基于分布式文件存储的数据库&#xff0c;一般用于存储…

PAT 1171 Replacement Selection

个人学习记录&#xff0c;代码难免不尽人意。 When the input is much too large to fit into memory, we have to do external sorting instead of internal sorting. One of the key steps in external sorting is to generate sets of sorted records (also called runs) wi…

Spring源码解析-构造函数

1、构造函数概述 构造函数中&#xff0c;主要创建两个对象分别用来读取注解参数和classpath下的文件 AnnotatedBeanDefinitionReader 专门读取注解参数的Reader ClassPathBeanDefinitionScanner 专门读取classpath下的文件&#xff0c;例如yml、properties等。 AnnotationC…

Unity 切换场景后场景变暗

问题 Unity版本&#xff1a;2019.4.34f1c1 主场景只有UI&#xff0c;没有灯光&#xff0c;天空盒&#xff1b;其他场景有灯光和天空盒所有场景不烘焙主场景作为启动场景运行&#xff0c;切换到其他场景&#xff0c;场景变暗某一个场景作为启动场景运行&#xff0c;光影效果正…

第P3周:天气识别

一、前期准备 1、设置GPU import torch import torch.nn as nn import torchvision.transforms as transforms import torchvision from torchvision import transforms, datasetsimport os,PIL,pathlibdevice torch.device("cuda" if torch.cuda.is_available() …

【探索Linux】—— 强大的命令行工具 P.7(进程 · 进程的概念)

阅读导航 前言一、冯诺依曼体系结构二、操作系统&#xff08;OS&#xff09;1. 概念 三、进程1. 进程的概念2. PCB&#xff08;Process Control Block&#xff09;3. 查看进程 四、fork函数1. 函数简介2. 调用方式3. 返回值4. 使用示例 五、进程的几种状态1. 状态简介2. 进程状…

SQLServer如何获取客户端IP

SQLServer如何获取客户端IP 很多用户询问如何通过SQLServer获取客户端IP从而定位一些问题&#xff0c;比如链接泄露&#xff0c;其实主要是利用几个相关视图&#xff0c;如下给出一些SQL方便用户排查 当前链接 SELECT CONNECTIONPROPERTY(PROTOCOL_TYPE) AS PROTOCOL_TYPE,CO…

Redis 数据类型详细解析

Redis是一个开源的、内存中的数据结构存储系统&#xff0c;可用作数据库、缓存和消息代理。Redis支持多种类型的数据结构&#xff0c;包括字符串&#xff08;String&#xff09;、哈希&#xff08;Hashes&#xff09;、列表&#xff08;Lists&#xff09;、集合&#xff08;Set…

sql中的排序函数dense_rank(),RANK()和row_number()

dense_rank()&#xff0c;RANK()和row_number()是SQL中的排序函数。 为方便后面的函数差异比对清晰直观&#xff0c;准备数据表如下&#xff1a; 1.dense_rank() 函数语法&#xff1a;dense_rank() over( order by 列名 【desc/asc】) DENSE_RANK()是连续排序&#xff0c;比如…