动态库和静态库

news2024/12/27 2:12:58

文章目录

    • 感性认识库
      • 动态库和静态库
    • 从库的设计者来看库
      • 制作静态库
      • 制作动态库
    • 库的使用者的角度
      • 静态库的使用
      • 使用动态库
        • 1.把头文件和库文件拷贝到系统的路径下
        • 2.修改对应的环境变量
        • 更改文件 ld.so.conf.d
        • 在系统的库路径下建立对应的软链接
    • 动态库如何加载

感性认识库

首先,对于库这个东西,我们是既熟悉又陌生。熟悉是因为我们每天基本都在用,C语言标准库,C++的STL类库等等的库。但是陌生的是,我们仅仅只是懂得使用这些库,而对库的整个原理我们是一无所知的!所以接下来,我们就先来感性认识一下库。
首先,来看一段最基本的C语言代码

#include<stdio.h>
int main()
{  
   printf("hello world\n");
   return 0;
}

这个C语言代码我们在熟悉不过了。那么这个代码最终如何变成最终的可执行程序的流程我们也很清楚。
预处理,编译,汇编,链接,而今天我们的研究的重点就是这个链接。
首先,我们要知道,首先我们要包<stdio.h>这个头文件,是因为我们使用了这个函数,而这个头文件里面有printf函数的声明! 记住,头文件里面有的仅仅只是函数的声明,所有函数的实现都是在头文件里面的! 但是问题是,没有函数的实现我怎么使用这个函数呢?别忘了,我们还有一步叫做链接。所谓的链接,就是对应去找你调用的函数的实现细节,只不过这些实现细节是以二进制代码的形式呈现的!而这些函数实现的二进制代码我们就称之为库。

简单来说:库就是函数实现的二进制代码的集合

动态库和静态库

感性认识了库以后。我们接下来具体讲一讲库的分类。
库一般有两种:一种叫做静态库,另外一种叫做动态库。 所谓的静态库,就是在链接这一步的时候,把对应库里面的代码拷贝到自己的代码里面去。那么静态库的优点就是在运行的时候不会依赖于对应的库,但是缺点就是会使得代码变得庞大。
而与之相反的则就是动态库,动态库就是在链接的时候去对应的库里面寻找对应的函数的实现代码,这样做的好处是节约空间,能够让多份代码共享同一个库。但不好的地方就是对库的依赖性强!
而在Linux下,动态库是以.so为后缀,静态库则是以.a作为后缀名
在windows下,动态库的后缀是.dll,静态库的后缀是.lib
那么接下来我们就从库的设计者和库的使用者两个角度来看库。首先我们先以库的设计者的角度切入,看看在Linux下如何制作动静态库

从库的设计者来看库

制作静态库

我们规范一点,我们按照C语言给我们制作库的方式。制作对应的头文件,还有制作对应的库文件。我们先以制作静态库为例

/*
  先提供库函数的实现
*/
#include "getMax.h"
int getMax(int* a,int n)
{ 
  assert(a);
  int i=1;
  int max=a[0];
  for( i=1;i<n;++i)
  {
     if(max < a[i])
       max=a[i];
  }
  return max;
}


而对应的,我们还需要头文件,这里也简单提供一下头文件

//提供库函数的声明
#pragma once 
#include<assert.h>
extern int getMax(int* a,int n); 

完成,这一简单的动作以后,接下来我们就要把函数文件进行编译了。使用的是gcc编译器的-c选项生成二进制目标文件

gcc -c getMax.c #默认就是形成同名的二进制目标文件

首先,接下来我们创建一个简单的使用这个函数的文件

#include "getMax.h"
#include<stdio.h>
int main()
{ 
  int a[]={11,22,33,44};
  int max=getMax(a,4);
  printf("max=%d\n",max);
  return 0;
}

在正式弄出静态库之前,我们不妨先来做个小实验。假如我就是把编译后的二进制文件直接交给对方进行链接,能否可以运行成功呢?说干就干
在这里插入图片描述

gcc -c test.o getMax.o

在这里插入图片描述
我们看到,程序不仅编译通过。而且运行的结果是正确的,也就是说直接把二进制文件交付给对方也是可以的! 但是为什么要有库的存在呢?因为未来的开发中,实际有的二进制文件可能有几百个甚至是上千个,而一股脑把这么庞大的二进制文件数量交给对方实在是不妥。所以,在实际的开发中。我们都会把二进制文件打包成库 接下来我们就来看怎么打包库。首先我们先从打包静态库开始。首先一样,我们要先把对应的文件编译成二进制文件。为了能够更好演示,所以我们这里多增加一个getMin函数

#include "getMin.h"
int getMin(int* a,int n)
{ 
  assert(a);
  int i=1;
  int min=a[0];
  for(i=1;i<n;++i)
  {
    if(min>a[i])
      min=a[i];
  }
  return min;
}

对应的getmin.h的内容如下:

#pragma once 
#include<assert.h>
extern int getMin(int* a,int n);

接下来我们就来看一看如何打包成静态库。首先第一步就是要把对应的头文件创建好,然后就是把对应函数的实现编译成二进制文件即可。
接下来使用的就是ar归档命令

#归档命令
ar -rc lib[libname].a objfile1 [...]

而接下来我们使用makefile来完成这些工作:

libgetMax.a:getMax.o getMin.o
	ar -rc libgetMax.a  getMax.o getMin.o
getMax.o:getMax.c
	gcc -c getMax.c
getMin.o:getMin.c
	gcc -c getMin.c
.PHONY:static
static:
	mkdir -p lib-static/lib 
	mkdir -p lib-static/include
	cp *.a  lib-static/lib
	cp *.h lib-static/include
.PHONY:clean
clean:	
	rm -rf *.a *.o lib-static

运行以后,对应的当前目录就会多一个lib-static的目录文件,里面包括了对应的头文件和库文件。
在这里插入图片描述
我们查看一下lib-static目录的内容:
在这里插入图片描述
这就是生成静态库。接下来我们来看一看如何制作动态库。

制作动态库

动态库的制作于静态库的制作方式十分相似。相对而言只不过是使用的一些gcc的选项不同罢了! 第一步就需要先把对应的原文件编译成二进制目标文件

#生成动态库的二进制文件需要使用选项-fPIC
gcc -fpic -c getMax.c
gcc -fpic -c getMin.c

这里的fpic选项的专业属于是与位置无关码,简单来讲就是生成的二进制代码的装入方式是和程序位置无关的!也就是说是相对地址的方式装入。不过这些都不是我们今天的关注的重点。 要生成动态库,就必须带上-fpic选项来对对应的源文件进行编译!
而接下来,我们就要进行动态库的生成使用如下的命令:

#生成对应的动态库
gcc -shared -o libgetMax.so getMax.o getMin.o

接下来,我们就来规范整个过程,makefile文件内容如下:

libgetMax.so:getMax.o getMin.o
libgetMax.so:	
	gcc -shared -o libgetMax.so getMax.o getMin.o
getMax.o:getMax.c
	gcc -fpic -c getMax.c
getMin.o:getMin.c
	gcc -fpic -c getMin.c
.PHONY:dynamic
dynamic:
	mkdir -p lib-dynamic/lib 
	mkdir -p lib-dynamic/include
	cp *.so  lib-dynamic/lib
	cp *.h lib-dynamic/include
.PHONY:clean
clean:
	rm -rf *.so *.o lib-dynamic

运行以后:
在这里插入图片描述
我们再来看一看对应的lib-dynamic的内容是什么:
在这里插入图片描述
到这里,动态库的打包形成也做好了。接下来,我们更改makefile,一次把静态库和动态库都生成了。

.PHONY:all
all:libgetMax.so libgetMax.a
libgetMax.so:getMax.o getMin.o
	gcc -shared -o libgetMax.so getMax.o getMin.o
getMax.o:getMax.c
	gcc -fpic -c getMax.c -o getMax.o
getMin.o:getMin.c
	gcc -fpic -c getMin.c -o getMin.o
	
libgetMax.a:getMax_s.o getMin_s.o
	ar -rc libgetMax.a getMax_s.o getMin_s.o
getMax_s.o:getMax.c
	gcc -c getMax.c -o getMax_s.o
getMin_s.o:getMin.c
	gcc -c getMin.c -o getMin_s.o
	
.PHONY:lib
lib:
	mkdir -p lib-static/lib
	mkdir -p lib-static/include 
	cp *.a lib-static/lib 
	cp *.h lib-static/include
	mkdir -p lib-dynamic/lib
	mkdir -p lib-dynamic/include 
	cp *.so lib-dynamic/lib 
	cp *.h lib-dynamic/include

.PHONY:clean
clean:
	rm -rf *.a *.so *.o lib-static lib-dynamic

在这里插入图片描述
可以看到,这里一次性生成了动态库和静态库。对于如何制作动静态库至此就已经告一段落了! 后面我们就要从库的使用者角度来看库了。

库的使用者的角度

接下来我们从使用者的角度来看待库。前面我们打包了动静态库,而动静态库的最主要的目的就是使用的。所以接下来我们来看一看怎么使用动静态库。

静态库的使用

首先我们先来看静态库如何使用,我们写下如下的测试代码:

#include "getMax.h"
#include<stdio.h>
int main()
{ 
  int a[]={11,22,33,44};
  int max=getMax(a,4);
  printf("max=%d\n",max);
  return 0;
}

使用gcc编译,发现编译失败,失败信息如下:
在这里插入图片描述
发现gcc并不认识这个头文件!这就和头文件的搜索策略有关了!""的方式的搜索策略优先从当前路径寻找头文件,然后就是从系统的路径寻找头文件。然而实际的情况是getMax头文件既不在当前路径,也不在系统的路径里面,所以就会报错! 在这种情况下,我们就要手动告诉gcc头文件的搜索路径。使用的就是-I选项。

#使用-I选项指定gcc的头文件搜索路径
gcc -I ../lib-static/include

结果如下:
在这里插入图片描述
虽然依旧是报错,但是可以明显发现:此时的报错已经和头文件没有关系了,而是对于getMax未定义的引用。这个报错是一个链接错误。说明gcc没办法找到这个函数的实现!
我们知道,形成可执行程序需要链接。而由于我们先前使用的都是C/C++的官方库,而这些库gcc/g++默认都认识!而我们今天使用的是自己的库,所以需要告诉gcc库的搜索路径还有需要链接的是哪一个库!所以需要带上下面的链接命令:

#-I跟的是头文件的搜索路径,-L跟的是库文件的搜索路径,-l表示的需要链接的库的名字
gcc test.c -I ../lib-static/include -L ../lib-static/lib/ -lgetMax -o test

在这里插入图片描述
可以看到,不仅生成了可执行程序,而且运行出了正确的结果!这就是使用静态库的方式! 下面我们来看一看如何使用动态库

使用动态库

使用动态库的方式和使用静态库的方式是一摸一样的,我们重复前面的步骤:

gcc test.c -I ../lib-static/include -L ../lib-static/lib/ -lgetMax -o test

在这里插入图片描述
但是运行起来的时候,发现报错!具体的报错原因如下:
在这里插入图片描述
什么?无法打开共享库文件?不存在这个文件!我们使用ldd命令来查看这个程序的动态库依赖:
在这里插入图片描述
为什么getMax库会找不到呢?明明已经把路径都指明了,为什么还找不到呢?问题是我们前面做的一切都是给gcc做的,gcc确实知道了库的路径,也帮我们生成了可执行程序。但是这些都是告诉gcc的!可执行程序跑起来变成进程以后,依旧要自己去寻找库的路径! 而如下有四种方式来帮助可执行程序寻找动态库:

1.把头文件和库文件拷贝到系统的路径下

首先,系统的头文件在下面的路径下:

/usr/include ---->系统头文件路径

而对应的库文件的路径是:

/lib64 ---->系统的库文件路径

而所谓的安装和卸载库就是把库拷贝到/lib64路径下,由于这种做法不够好,会污染系统的环境,所以这种方式不推荐。感兴趣的读者可以自己实现

2.修改对应的环境变量

实际上,系统每一次加载动态库都会从一个环境变量中读取,这个环境变量
叫做:LD_LIBRARY_PATH 我们只要讲我们对应的库的路径导入到这个变量里面就可以了,即:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH : (lib_path)

执行这条命令以后,查看对应的程序的动态库信息:
在这里插入图片描述
但是,首先这个环境变量的名字很难记住,另外这种命令行式的导入下次重新启动就失效了!所以这种方式其实也是相对比较吃力不讨好 接下来我们介绍第三种方式

更改文件 ld.so.conf.d

在系统的etc目录下存在 ld.so.conf.d 这个文件夹,这个文件夹下存储的是一系列的配置文件:
在这里插入图片描述
其实里面的存储的内容都特别简单:存储的就是对应库文件的绝对路径!接下来我们也可以把我们的getMax函数库的路径写到这个文件夹里面的配置文件里,后面我们的动态库就可以查找到这个文件了
首先,创建一个ld.so.conf.d路径下创建一个配置文件:
在这里插入图片描述
接下来,往配置文件里面写入对应的动态库的路径信息:建议使用绝对路径!
在这里插入图片描述
这里我已经写入了。
保存退出以后,使用ldconfig命令是配置立即生效:
此时我们使用ldd查看对应的test程序的动态库依赖关系:
在这里插入图片描述
可以看到这个时候,能够找到对应的getMax库了!而且这种方式的修改是永久的!即使退出本shell,下次登陆也是有效的!

在系统的库路径下建立对应的软链接

最后一种方式,也是官方库里面用的相对较多的一种方式:虽然直接在系统路径下直接拷贝库文件不太好,但是可以在系统的路径下建议软链接来代替直接拷贝库!这里我们也选择建立一个软链接指向我们的库

sudo ln -s [绝对路径]  /lib64/软链接名

在这里插入图片描述
这个时候我们在使用ldd命令观察对应的test程序的动态库依赖:
在这里插入图片描述
可以看到,此时我们的程序依旧是正确找到了对应的对应的动态库信息。所以在系统库路径直接建立软连接的方式也是可以的!

动态库如何加载

接下来,我们从原理层面来看动态库是如何加载的。
首先,我们还是把进程,进程地址空间,物理内存,页表,还有磁盘全部画出来:
在这里插入图片描述
而另外一个进程来,对应做的就是已经加载进物理内存的库的物理内存地址和自己进程地址空间的虚拟地址之间的映射关系使用页表维护好,这样物理内存里面就会只有一份库文件!达到了节约资源的目的
但是,还有一个问题:那么就是要加载库的前提是要先找到这个库,虽然操作系统每次都会从默认的路径去寻找,但是如果默认路径找不到,最终操作系统也就会找不到!所以动态库需要告诉系统对应的库在哪里,以方便操作系统把库文件载入内存!这就是为什么动态库在运行的时候常常找不到的原因!静态库就因为是把代码拷贝进自己的进程PCB中去,所以没有这些问题!
这就是本文的主要内容,如有不足之处,还望指出。希望大家共同进步。

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

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

相关文章

关于GNSS关键性能测试,应该如何选择?

现在&#xff0c;GPS/GNSS信号无处不在&#xff0c;而GNSS接收机芯片的低成本和高性能也让在不同设备中安装GNSS接收机变得更为容易。然而&#xff0c;与此同时又存在一个问题&#xff0c;如果想将这些接收器芯片集成到一个设备或系统中&#xff0c;该如何对其进行全面的GNSS测…

方法注解@Bean与对象注入

1.类注解是添加到某个类上的&#xff0c;⽽⽅法注解是放到某个⽅法上的&#xff0c;如以下代码的实现&#xff1a; 注&#xff1a;方法注解一定要搭配类注解使用&#xff0c;否则会报错 2.重命名Bean 多个重命名&#xff0c;使用多个名称获取的对象都是同一个 3.获取 Bean 对象…

全网惟一面向软件测试人员的Python基础教程-在Python中列表是什么?

全网惟一面向软件测试人员的Python基础教程 起点&#xff1a;《python软件测试实战宝典》介绍 第一章 为什么软件测试人员要学习Python 第二章 学Python之前要搞懂的道理 第三章 你知道Python代码是怎样运行的吗&#xff1f; 第四章 Python数据类型中有那些故事呢&#xff1f;…

TTL(RGB)接口液晶显示屏的调试方法

TTL接口的液晶显示屏一般会使用DE模式驱动它。首先需要CPU带有LCD控制器&#xff0c;能够产生出液晶显示屏所需要的以下几个信号&#xff1a; 1.时钟信号(DOTCLK) 2.行同步信号(HSYNC) 3.场同步信号(VSYNC) 4.DEN(数据允许信号) 6.数据信号&#xff08;R0-R7;G0-G7;B0-B7)…

第02讲:Git分支操作

一、什么是分支 在版本控制过程中&#xff0c;同时推进多个任务&#xff0c;为每个任务&#xff0c;我们就可以创建每个任务的单独 分支。使用分支意味着程序员可以把自己的工作从开发主线上分离开来&#xff0c;开发自己分支的时 候&#xff0c;不会影响主线分支的运行。对于…

剑指 Offer 17. 打印从1到最大的n位数

题目 输入数字 n&#xff0c;按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3&#xff0c;则打印出 1、2、3 一直到最大的 3 位数 999。 思路 本题应该考虑的是大数问题&#xff0c;但是返回数组为int[]。。。所以两种方法都做一下 方法一&#xff1a;普通解法&#xff…

天翼云服务器性能评测,4H8G贵阳节点性能跑分

天翼云号称全球运营商云第一、中国公有云用户数第二、政务云公有云第一。那么天翼云服务器用起来到底怎么样呢&#xff1f; 目前&#xff0c;蓝队云这边一共有19个天翼云节点云服务器&#xff0c;覆盖全国多个核心省市及地区&#xff0c;节点选择的话一般就是按照就近原则。 …

权威报告出炉:2022年移动机器人出货量增长53%!2023年移动机器人迎来发展新拐点?

原创/文 BFT机器人 近日&#xff0c;全球权威研究机构Interact Analysis重磅发布移动机器人领域的“风向标”——《2022年移动机器人市场报告》&#xff08;The Mobile Robot Market 2022&#xff09;&#xff0c;深入探究真实市场数据&#xff0c;对全球移动机器人领域的发展现…

当压力传感器遇到汽车电子系统 智芯传感打造车规级智能感知传感器产品

汽车电子系统是汽车数字化的发端和基础&#xff0c;更是汽车智能化的前提。近年来&#xff0c;伴随着汽车电子技术的快速发展与应用&#xff0c;汽车电子系统在车辆控制精度、范围、适应性和智能化等方面&#xff0c;都实现了大幅优化提升。而压力传感器作为汽车电子系统的主要…

RHCSA 第五天笔记

用户和组管理 用户分为三类&#xff1a; 超级用户 root 普通用户 只能切换到自己有的用户 系统用户&#xff08;不常用&#xff0c;没办法登录&#xff09; 组分为两类 定义概念 基本组&#xff08;私有组&#xff09; 没指定所属组&#xff0c;系统建立和用户同名的组 附加…

诺依文件上传支持阿里云oss

文章目录描述后端引入依赖application.yml 添加访问oss需要的参数编写oss工具类编写controller前端更改调用接口![在这里插入图片描述](https://img-blog.csdnimg.cn/efc28eb8244e4da482d46327ab29a999.png)去掉baseUrl回调成功后url改成真实urlmain.js中挂在该组件具体调用代码…

华为手表开发:GT3(1)配置调试设备

华为手表开发&#xff1a;GT3&#xff08;1&#xff09;配置调试设备初环境与设备获取手表UUID登录 AppGallery Connect 点击用户与访问初 希望能写一些简单的教程和案例分享给需要的人 环境与设备 系统&#xff1a;window 设备&#xff1a;HUAWEI WATCH 3 Pro 开发工具&…

自动化测试到底该怎么学?

从功能测试到自动化测试 &#xff0c;待遇翻倍&#xff0c;我整理的超全学习指南&#xff01; 首选你需要拥有一个全网最牛最全面的软件测试全职业生涯进阶从零基础到测试开发学习路线图。 其次跟着大纲的内容慢慢学习&#xff0c;当然配套的【教学视频配套的学习资料】肯定是…

代码随想录二刷day4 24.两两交换链表中的结点 19.删除链表的倒数第n个结点 链表相交(环形链表)

二刷复习 文章目录二刷复习24.两两交换链表中的结点19.删除链表的倒数第n个结点链表相交环形链表224.两两交换链表中的结点 思路还是挺简单的&#xff0c;在纸上画一个图就行了 思考的过程&#xff1a; 1.我会思考需要用一个指针curr还是两个指针prev和curr&#xff0c;思考的…

利用 Nacos 实现了一个动态化线程池,非常实用

在后台开发中&#xff0c;会经常用到线程池技术&#xff0c;对于线程池核心参数的配置很大程度上依靠经验。然而&#xff0c;由于系统运行过程中存在的不确定性&#xff0c;我们很难一劳永逸地规划一个合理的线程池参数。在对线程池配置参数进行调整时&#xff0c;一般需要对服…

SegeX Automation:VC调用Golden Surfer自动化失败(VC调用Automation失败)原因详解

----哆啦刘小洋 原创&#xff0c;转载需说明出处 2022-12-29 SegeX Automation:Surfer自动化&#xff08;Automation&#xff09;失败原因1 简介2 初始化Surfer对象不成功2.1 一般代码2.1 改进代码3 Windows系统原因4 Surfer原因5 其他问题1 简介 在工程领域&#xff0c;Golde…

国产无线耳机什么牌子好?国产真无线蓝牙耳机排行

随着近几年蓝牙耳机市场的快速发展&#xff0c;蓝牙耳机依旧占据着半个行业的份额&#xff0c;在人们的日常生活中出现的频率也越来越高。叫得上名的国产蓝牙耳机牌子也越来越多&#xff0c;那么&#xff0c;国产无线耳机什么牌子好&#xff1f;下面&#xff0c;我来给大家推荐…

无代码资讯 | Gartner 预测明年低代码市场规模;微软推出低代码学习平台;AWS 推出开发无服务器应用的低代码平台

栏目导读&#xff1a;无代码资讯栏目从全球视角出发&#xff0c;带您了解无代码相关最新资讯。 TOP3 大事件 1、Gartner 预测到 2023 年&#xff0c;全球低代码市场预计达到269亿美元 根据 Gartner 的最新预测&#xff0c;到 2023 年全球低代码开发技术市场规模预计将达到 269…

hnu计网实验三-应用层和传输层协议分析(PacketTracer)

前言&#xff1a;难度适中的一个实验 一、实验目的 通过本实验&#xff0c;熟悉PacketTracer的使用&#xff0c;学习在PacketTracer中仿真分析应用层和传输层协议&#xff0c;进一步加深对协议工作过程的理解。 二、实验内容 研究应用层和传输层协议 从 PC 使用 URL 捕获 W…

多道程序与分时多任务--rCore[3]

概念 协作式操作系统 计算机硬件在快速发展&#xff0c;内存容量在逐渐增大&#xff0c;处理器的速度也在增加&#xff0c;外设 I/O 性能方面的进展不大。于是就想到&#xff0c;让应用在执行 I/O 操作或空闲时&#xff0c;可以主动 释放处理器 &#xff0c;让其他应用继续执行…