【Linux】动态库与静态库

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

🌝每一个不曾起舞的日子,都是对生命的辜负


目录

前言

1.为什么要有库?

2.认识动静态库

3.动静态库的对比

3.1静态库

3.2动态库

3.2.1动态库链接过程图示

4.静态库打包与使用

4.1打包

 4.2使用

5.动态库打包与使用

5.1打包

5.2使用


前言

本篇文章博主会与大家共同学习动静态库的相关内容,涉及到库的基本知识,动静态库的安装等内容。


欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。 

=========================================================================

GITEE相关代码:🌟樊飞 (fanfei_c) - Gitee.com🌟

=========================================================================


1.为什么要有库?

在项目中,往往会有很多的 xxx.c 和 xxx.h 文件构成,xxx.c文件可以编译生成 xxx.o文件,最后经过链接形成可执行程序。

那么实际上链接的对象就是这些 xxx.o文件。

但一般在项目中会有很多的 xxx.c 文件,相应的也会生成很多的 xxx.o文件,但这么多 xxx.o文件我们不好做管理,所以我们可以将这些 xxx.o文件打包成库,这样我们只需要提供头文件.h和一个库就可以了。

这样做的目的或者说库存在的意义就是:提高开发效率、隐藏源代码。

实际上,所有库本质都是一堆目标文件(xxx.o)的集合,库的文件当中并不包含主函数而只是包含了大量的方法以供调用。


2.认识动静态库

在linux下,我们可以通过『 ldd 文件名 』来查看一个可执行程序所依赖的库。

这里我们写一段简单的代码,观察一下程序链接的库。

#include <stdio.h>

int main()
{
    printf("hello world\n");//调用库函数
    return 0;
}

 那么libc.so.6就是库。

  • 在Linux当中,以.so为后缀的是动态库,以.a为后缀的是静态库。
  • 在Windows当中,以.dll为后缀的是动态库,以.lib为后缀的是静态库。

 这里可执行程序所依赖的libc.so.6实际上就是C动态库。

当我们去掉一个动静态库的前缀lib,再去掉后缀.so或者.a及其后面的版本号,剩下的就是这个库的名字

注意:库的名字不是libc.so.6,而是 c 

另外gcc/g++编译器默认都是动态链接的,若想进行静态链接,可以携带一个『 -static』选项。

并且静态链接由于是将库的内容整合到程序中,所以静态链接产生的程序不依赖库文件,我们同样可以用『 ldd 文件名』来验证。

此外,当我们分别查看动静态链接生成的可执行程序的文件类型时,也可以看到它们分别是动态链接和静态链接的。 


3.动静态库的对比

3.1静态库

静态库是程序在编译链接的时候把库的代码复制到可执行文件当中的,生成的可执行程序在运行的时候将不再需要静态库,因此使用静态库生成的可执行程序的大小一般比较大。

优点:程序不依赖任何库,可以独自运行。

缺点:会占用大量空间,特别是如果该库经常被链接,那么此时内存中就会存在大量重复代码。


3.2动态库

动态库是程序在运行时被链接的,生成的可执行程序需要依赖动态库,当多个程序链接该动态库时,共用一个库即可,这样达到了节省空间,减少重复代码的目的。

优点:节省磁盘空间,且多个用到相同动态库的程序同时运行时,库文件会通过进程地址空间进行共享,内存当中不会存在重复代码。

缺点:需要依赖动态库,否则无法运行。


3.2.1动态库链接过程图示

我们知道库文件存储在磁盘,当程序运行需要使用动态库时,OS将磁盘中的库文件读取到物理内存中,结合我们之前学习的进程地址空间部分的内容,我们知道进程与物理内存之间还存在有进程地址空间和页表映射,所以大致的链接过程图示如下:


4.静态库打包与使用

构建模型研究动静态库的打包与使用:

其中两个源文件add.csub.c,两个头文件add.hsub.h。

  • add.h当中的内容如下:
    #pragma once
    
    extern int my_add(int x, int y);
  • add.c当中的内容如下:
    #include "add.h"
    
    int my_add(int x, int y)
    {
        return x + y;
    }
  • sub.h当中的内容如下:
    #pragma once
    
    extern int my_sub(int x, int y);
  • sub.c当中的内容如下:
    #include "sub.h"
    
    int my_sub(int x, int y)
    {
        return x - y;
    }

下面有关动静态库的打包和使用我们都以这四个文件为模型研究。 

4.1打包

1.编译生成『 .o』目标文件

2.使用『 ar』命令将所有『 .o』文件打包为静态库

ar命令是gnu的归档工具,常用于将目标文件打包为静态库,下面我们使用ar命令的-r选项和-c选项进行打包。

  • -r(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。
  • -c(create):建立静态库文件。

实例:

将『 add.o』和『 sub.o』打包为静态库,静态库名为『 cal 』。

ar -rc libcal.a add.o sub.o

ar指令的其他参数

  • -t:列出静态库中的文件。
  • -v(verbose):显示详细的信息。
ar -tv libcal.a

3.标准化库 

库的标准化:一个文件夹下面放的是一堆头文件的集合,另一个文件夹下面放的是库文件。

因此,在这里我们可以将add.h和sub.h这两个头文件放到一个名为include的目录下,将生成的静态库文件libcal.a放到一个名为lib的目录下,然后将这两个目录都放到mylib下,此时就可以将mylib给别人使用了。

当然在未来传输文件时一般都是采用压缩包的形式,所以我们可以将这个库压缩一下:

tar czf mylib.tgz mylib

在未来打包库时基本就采用这样的步骤,那么为了避免我们重复操作,可以使用makefile简化我们的操作 :

使用『 make』命令可以生成静态库文件, 使用『 make output』命令可以标准化库。 


 4.2使用

为了研究动静态库的使用我们创建一个测试代码test.c:

#include <stdio.h>
#include <add.h>

int main()
{
	int x = 20;
	int y = 10;
	int z = my_add(x, y);
	printf("%d + %d = %d\n", x, y, z);
	return 0;
}

1. 使用选项   这个是大写的i->『 -I』、『 -L』、『 -l』<- 这个是L的小写

此时使用gcc编译test.c生成可执行程序时需要携带三个选项:

  • -I  (这个是i的大写):指定头文件搜索路径。
  • -L:指定库文件搜索路径。
  • -l  (这个是L的小写):指明需要链接库文件路径下的哪一个库 (l后直接加库的名字,注意库的名字要去掉lib和后缀,比如libcal.a,库名就是cal)。
gcc test.c -I./mylib/include -L./mylib/lib -lcal

成功执行。 

注:-I-L-l这三个选项后面可以加空格,也可以不加空格。

 2.将头文件和库文件拷贝到系统库路径下 

上面是因为OS找不到库,所以我们需要给以上三种参数赋值来告诉OS头文件位置和库的位置,但是OS会默认到系统库目录和头文件目录去找,所以我们可以将头文件和库拷贝到系统目录中。

注:系统中头文件位置/usr/include,库位置/lib64

sudo cp mylib/include/* /usr/include/ 
sudo cp mylib/lib/libcal.a /lib64/

需要注意的是,虽然已经将头文件和库文件拷贝到系统路径下,但当我们使用gcc编译main.c生成可执行程序时,还是需要指明需要链接库文件路径下的哪一个库:

gcc test.c -lcal

那之前我们编译C程序时为什么没有指定库呢? 

因为我们使用gcc编译的是C语言,而gcc就是用来编译C程序的,所以gcc编译的时候默认就找的是C库,但此时我们要链接的是哪一个库编译器是不知道的,因此我们还是需要使用-l选项,指明需要链接库文件路径下的哪一个库。

注意:实际上我们『 拷贝』头文件和库文件到系统路径下的过程,就是『 安装』库的过程。但并不推荐将自己写的头文件和库文件拷贝到系统路径下,这样做会对系统文件造成污染


5.动态库打包与使用

5.1打包

1.编译生成『 .o』目标文件

此时用源文件生成目标文件时需要携带-fPIC选项:

  • -fPIC(position independent code):产生位置无关码。

说明一下:

  1. -fPIC作用于编译阶段,告诉编译器产生与位置无关的代码,此时产生的代码中没有绝对地址,全部都使用相对地址,从而代码可以被加载器加载到内存的任意位置都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
  2. 如果不加-fPIC选项,则加载.so文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的拷贝,并且每个拷贝都不一样,取决于这个.so文件代码段和数据段内存映射的位置。
  3. 不加-fPIC编译出来的.so是要在加载时根据加载到的位置再次重定位的,因为它里面的代码BBS位置无关代码。如果该.so文件被多个应用程序共同使用,那么它们必须每个程序维护一份.so的代码副本(因为.so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)。
  4. 我们总是用-fPIC来生成.so,但从来不用-fPIC来生成.a。但是.so一样可以不用-fPIC选项进行编译,只是这样的.so必须要在加载到用户程序的地址空间时重定向所有表目。

2. 使用『 -shared』选项将所有目标文件打包为动态库

与生成静态库不同的是,生成动态库时我们不必使用ar命令,我们只需使用gcc的-shared选项即可。

gcc -shared -o libcal.so add.o sub.o

3.标准化库 

与生成静态库时一样,为了方便别人使用,在这里我们可以将add.hsub.h这两个头文件放到一个名为include的目录下,将生成的动态库文件libcal.so放到一个名为lib的目录下,然后将这两个目录都放到mylib下,此时就可以将mylib给别人使用了。

同样的可以将这个库压缩一下:

tar czf mylib.tgz mylib

利用makefile:

使用『 make』命令可以生成静态库文件, 使用『 make output』命令可以标准化库。 


5.2使用

使用该动态库的方法与刚才我们使用静态库的方法一样,我们既可以使用 『 -I』,『 -L』,『 -l』这三个选项来生成可执行程序,也可以先将头文件和库文件拷贝到系统目录下,然后仅使用-l选项指明需要链接的库名字来生成可执行程序,下面我们仅以第一种方法为例进行演示。

此时使用gcc编译main.c生成可执行程序时,需要用-I选项指定头文件搜索路径,用-L选项指定库文件搜索路径,最后用-l选项指明需要链接库文件路径下的哪一个库。

gcc test.c -I./mylib/include -L./mylib/lib -lcal

编译成功生成a.out文件,可是在执行的时候却出现了问题:

ldd查看一下依赖的库:

 可以看到,此时可执行程序所依赖的动态库是没有被找到的。

所以为了解决找不到动态库的问题,有以下几种解决方案:

1.拷贝.so文件到系统共享库路径下

sudo cp mylib/lib/libcal.so /lib64

2.更改LD_LIBRARY_PATH

LD_LIBRARY_PATH是程序运行动态查找库时所要搜索的路径。

我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH环境变量当中即可。

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ff/test_24_03_15/mylib/lib

注意:导入环境变量记得不要覆盖,而是追加:

export LD_LIBRARY_PATH=&LD_LIBRARY_PATH:+要加的路径。

3.ldconfig 配置/etc/ld.so.conf.d/ 

我们可以通过配置/etc/ld.so.conf.d/的方式解决该问题,/etc/ld.so.conf.d/路径下存放的全部都是以.conf为后缀的配置文件,而这些配置文件当中存放的都是路径,系统会自动在/etc/ld.so.conf.d/路径下找所有配置文件里面的路径,之后就会在每个路径下查找你所需要的库。我们若是将自己库文件的路径也放到该路径下,那么当可执行程序运行时,系统就能够找到我们的库文件了。

首先将库文件所在目录的路径存入一个以.conf为后缀的文件当中。

echo /home/ff/test_24_03_15/mylib/lib > ff.conf

然后将该.conf文件拷贝到/etc/ld.so.conf.d/目录下。

sudo cp ff.conf /etc/ld.so.conf.d

最后利用『 ldconfig』命令将配置文件更新一下,更新之后系统就可以找到该可执行程序所依赖的动态库了。

sudo ldconfig

=========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================

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

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

相关文章

数字人克隆系统源码部署,该如何选择源码厂家

短视频领域成2024年风口&#xff0c;数字化概念成为我们生活中不可或缺的一部分。在数字化的大潮中&#xff0c;有很多创业者开始瞄准了数字人源码部署这个商业风口项目。而许多企业开始考虑采用数字人系统进行业务拓展和服务升级&#xff0c;那么如何选择适合自己的数字人源码…

立白打造统一数据工作台,报表查看更方便,领导决策更高效!

数字化时代&#xff0c;数据就好比隐藏的宝藏&#xff0c;“寻宝人”若想精准、便捷地探索宝藏&#xff0c;就需要应用清晰、明确的藏宝图——数据门户。 今天&#xff0c;小编就带领大家&#xff0c;看看作为国内500强的日化品牌如何携手迅易科技搭建和应用统一数据门户&…

专题三_二分查找(1)

目录 704. 二分查找 解析 题解 34. 在排序数组中查找元素的第一个和最后一个位置 解析 题解 69. x 的平方根 解析 题解 704. 二分查找 704. 二分查找 - 力扣&#xff08;LeetCode&#xff09; 解析 题解 class Solution { public:int search(vector<int>& …

【考研数学】零基础120+备考计划(附资源分享)

大学四年没学过数学&#xff0c;那数学就等于零基础 如果想参加考研数学&#xff0c;并且想获得一个比较好的分数&#xff0c;那我建议&#xff0c;一定要从头开始学&#xff0c;从最简单的开始学&#xff01; 在考研的选择上&#xff0c;我们都知道&#xff0c;有数学一、数…

【C++】一篇文章带你深入了解string

目录 一. 为什么学习string&#xff1f;二、 标准库中的string2.1 string介绍2.2 string的常用接口说明2.2.1 string对象的常见构造2.2.1.1 [string()](https://legacy.cplusplus.com/reference/string/string/string/) ---- 无参构造函数2.2.1.2 [string(const char* s)](http…

Vue限制文本框显示字数,多余用...代替

1.在filters.js封装过滤器方法 import Vue from vue//设置只显示几个字符串&#xff0c;默认20个 Vue.filter(filterAmount, function(value, n) {if(!n) n 20;if(value && value.length > n) {value value.substring(0, n) ...;}return value;} )2.在main.js引…

JavaEE 初阶篇-深入了解多线程安全问题(指令重排序、解决内存可见性与等待通知机制)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 指令重排序概述 1.1 指令重排序主要分为两种类型 1.2 指令重排序所引发的问题 2.0 内存可见性概述 2.1 导致内存可见性问题主要涉及两个方面 2.2 解决内存可见性问…

【计算机考研】 408难吗?学到什么程度才能考130?

如果你是92科班&#xff0c;我觉得130是有机会的 。如果双非或者跨考&#xff0c;我觉得是很难的。可以关注一下可软和软微的复试通知。可以看到数学130以上的大有人在&#xff0c;408能考到130的寥寥无几。 而且从今年来看&#xff0c;对于基础还行&#xff0c;只用王道的我来…

LeetCode算法——数组/字符串篇

对刷过的算法进行总结&#xff0c;所用解法都是最符合我个人逻辑的&#xff0c;以后再刷的话就看这篇帖子了 # 代码随想录——数组理论基础 首先要知道数组在内存中的存储方式&#xff0c;这样才能真正理解数组相关的面试题 数组是存放在连续内存空间上的相同类型数据的集合 …

蓝桥备赛——贪心

题干 AC Code n, w = map(int, input().split()) # n种类, w核载重 a = [] # [[weight1, value1], [weight2, value2], ...] for _ in range(n):a.append(list(map(int, input().split()))) a.sort(key=lambda x: x[1] / x[0], reverse=True)maxVal = 0for i in a:if i[0…

亮数据Bright Data,引领高效数据采集新体验

随着互联网和大数据的日益普及&#xff0c;我们对于高速、安全和无限畅通的网络体验追求越发迫切&#xff0c;随之而来的网络安全和隐私保护变得越来越重要。IP代理作为一种实用的代理工具&#xff0c;可以高效地帮我们实现网络数据采集&#xff0c;有效解决网络安全问题&#…

大数据量查询语句优化

测试单表模糊查询&#xff0c;符合条件的数量为&#xff1a; -- 查看总共有多少条数据 select count(0) from "REGISTER_HOUSE_INFO" where SEAT_NAME like %1% ;未优化&#xff1a;测试单表模糊查询分页&#xff0c;符合条件的数据为&#xff1a; select * from …

单词精灵,Android 记单词 app 开发

使用 Android Studio 开发了一款 记单词 app —— 《单词精灵》 关键词&#xff1a;单词精灵 A. 项目描述 《单词精灵》是一款专为Android平台设计的单机记单词应用。该应用旨在帮助用户系统、高效地扩展词汇量&#xff0c;提升英语水平。应用内置丰富的词库和记忆方法&#…

C++AVL树拓展之红黑树原理及源码模拟

前言&#xff1a;我们之前已经从零开始掌握AVL树http://t.csdnimg.cn/LaVCChttp://t.csdnimg.cn/LaVCC 现在我们将继续学习红黑树的原理并且实现插入等功能&#xff0c;学习本章的前提要求是掌握排序二叉树和AVL树&#xff0c;本章不再提及一些基础知识&#xff0c;防止本文结…

LeetCode-560. 和为 K 的子数组【数组 哈希表 前缀和】

LeetCode-560. 和为 K 的子数组【数组 哈希表 前缀和】 题目描述&#xff1a;解题思路一&#xff1a;一边算前缀和一边统计。这里用哈希表统计前缀和出现的次数&#xff0c;那么和为k的子数组的个数就是当前前缀和-k的个数&#xff0c;即preSums[presum - k]。画个图表述就是&a…

sparksql执行流程

1. SparkSQL的自动优化 我们前面的文章已经说过spark RDD定义好后&#xff0c;执行经过DAG sechduler划分号内存管道、逻辑任务&#xff0c;然后经由task scheduler来分配到具体worker来管理运行&#xff0c;RDD的运行会完全按照开发者的代码执行 如果开发者水平有限&#xff…

一文了解JAVA的常用API

目录 常用kpimathSystemRuntimeObjectObjectsBigIntegerBigDecima正则表达式包装类 常用kpi 学习目的&#xff1a; 了解类名和类的作用养成查阅api文档的习惯 math 工具类。因为是工具类&#xff0c;因此直接通过类名.方法名(形参)即可直接调用 abs&#xff1a;获取参数绝对…

Spring如何进行事务管理?什么是面向切面编程?

喜欢就点击上方关注我们吧&#xff01; 本篇将带你快速了解Spring事务管理以及面向切面编程(AOP)相关知识。 一、事务 1、概述 1&#xff09;事务是一组操作的集合&#xff0c;是一个不可分割的工作单位&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败。 2&#xff…

八股 -- C#

面向对象 &#xff08;三大特性&#xff09; 三大特性目的是为了提供更好的代码组织、可维护性、扩展性和重用性 C#基础——面向对象 - 知乎 (zhihu.com) 封装 理解&#xff1a; 你不需要了解这个方法里面写了什么代码&#xff0c;你只需要了解这个方法能够给你返回什么数据&…

矩阵乘法优化:GEMM中如何将大矩阵切割成小矩阵

论文自然还是 Anatomy of High-Performance Matrix Multiplication。 如何拆分 一个矩阵乘法有 6 种拆分方式&#xff0c;其中对 row-major 效率最高的是&#xff1a; 第一次拆分 先做第一次拆分&#xff0c;取 A 的 kc 列&#xff08;PanelA&#xff09;和 B 的 kc 行&…