【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 文件名 』来查看一个可执行程序所依赖的库。

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

cpp 复制代码
#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当中的内容如下:

    cpp 复制代码
    #pragma once
    
    extern int my_add(int x, int y);
  • add.c当中的内容如下:

    cpp 复制代码
    #include "add.h"
    
    int my_add(int x, int y)
    {
        return x + y;
    }
  • sub.h当中的内容如下:

    cpp 复制代码
    #pragma once
    
    extern int my_sub(int x, int y);
  • sub.c当中的内容如下:

    cpp 复制代码
    #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 』。

cpp 复制代码
ar -rc libcal.a add.o sub.o

ar指令的其他参数

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

3.标准化库

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

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

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

cpp 复制代码
tar czf mylib.tgz mylib

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

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


4.2使用

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

cpp 复制代码
#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)。
cpp 复制代码
gcc test.c -I./mylib/include -L./mylib/lib -lcal

成功执行。

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

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

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

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

cpp 复制代码
sudo cp mylib/include/* /usr/include/ 
cpp 复制代码
sudo cp mylib/lib/libcal.a /lib64/

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

cpp 复制代码
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选项即可。

cpp 复制代码
gcc -shared -o libcal.so add.o sub.o

3.标准化库

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

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

cpp 复制代码
tar czf mylib.tgz mylib

利用makefile:

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


5.2使用

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

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

cpp 复制代码
gcc test.c -I./mylib/include -L./mylib/lib -lcal

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

ldd查看一下依赖的库:

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

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

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

cpp 复制代码
sudo cp mylib/lib/libcal.so /lib64

2.更改LD_LIBRARY_PATH

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

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

cpp 复制代码
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为后缀的文件当中。

cpp 复制代码
echo /home/ff/test_24_03_15/mylib/lib > ff.conf

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

cpp 复制代码
sudo cp ff.conf /etc/ld.so.conf.d

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

cpp 复制代码
sudo ldconfig

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

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

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

🌟**~ 点赞收藏+关注 ~**🌟

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

相关推荐
C++忠实粉丝24 分钟前
Linux环境基础开发工具使用(2)
linux·运维·服务器
康熙38bdc1 小时前
Linux 环境变量
linux·运维·服务器
存储服务专家StorageExpert1 小时前
DELL SC compellent存储的四种访问方式
运维·服务器·存储维护·emc存储
hakesashou2 小时前
python如何比较字符串
linux·开发语言·python
Ljubim.te2 小时前
Linux基于CentOS学习【进程状态】【进程优先级】【调度与切换】【进程挂起】【进程饥饿】
linux·学习·centos
cooldream20092 小时前
Linux性能调优技巧
linux
大G哥2 小时前
记一次K8S 环境应用nginx stable-alpine 解析内部域名失败排查思路
运维·nginx·云原生·容器·kubernetes
长天一色2 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
醉颜凉2 小时前
银河麒麟桌面操作系统修改默认Shell为Bash
运维·服务器·开发语言·bash·kylin·国产化·银河麒麟操作系统
QMCY_jason3 小时前
Ubuntu 安装RUST
linux·ubuntu·rust