• 【Linux:动态库与静态库】


    1 动态库与静态库的概念

    • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
    • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码 。
    • 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking
    • 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。

     我们可以简单的

     看看我们常用的c/c++库:

     那库的名字是啥呢?

    我们拿上面图中最下面的两个来说明:

    libstdc++.so.6  libstdc++.so.6.0.19

    我们的规则是:去掉开头lib,去掉.so或者.a后的内容

    所以我们这里得到的库名字都是:stdc++ 

    一般来说云服务器只会默认存在动态库,静态库需要我们自己安装。

    C/C++静态库安装命令:

    1. sudo yum install -y glibc -static
    2. sudo yum install -y libstdc++ -static

    2 为什么要用库

    有了库,程序员开发时就能够少去不少麻烦,比如我们如果要自己写一个cout打印函数来帮助打印的话会浪费很多不必要的时间,所以我们一般写C/C++代码时喜欢包含头文件,头文件中就包含了一些常用的接口的声明,那么定义呢?

    其实接口的定义我们使用的编译器已经帮助我们将函数定义打好包安装到了默认的搜索路径下,当我们只要包含了头文件,在链接的时候就会去默认路径下找到接口定义的目标文件,然后链接。

    我们经常使用的VS2019叫做集成开发环境,其中包含了编辑器,头文件和库,这也是为什么在我们包含了头文件时写头文件中的接口时会有语法提醒,本质上其实就是在头文件中找到该接口。


    3 制作静态库

    我们首先创建4个文件,将四个文件中.c文件编译生成了.o文件后 ,将.o文件与.h文件交给otherUsr目录中:

     再将main.c拷过去:

     现在我们在otherUsr中运行:

     很显然此时能够运行成功。但是我们一般是把.o文件打包成一个库,打包命令为:

    ar -rc libXXX.a *.o

     我们可以来试试:

     这时我们为了规范性将libmymath.a放在一个名叫dir的目录下,将*.o的文件放在include的目录下:

     这时为了能够找到*.h文件和打包的库文件我们要加上3个选项来帮助编译器来找寻:

    1. -I *.h的文件路径
    2. -L 打包库的路径 //L后面空格可加可不加
    3. -l 打包库名 //l后面空格可加可不加,注意库名不包括lib前缀和.a后缀

     这样就能够成功运行了:

     但是为啥我们用C/C++的库就不用这么麻烦呢?原因C/C++的库是安装到指定路径下的,所以我们不用指定路径,如果我们想要将我们自己的库和头文件添加到系统默认配置里面可以用下面方法:

     

     其中系统默认的头文件安装路径是:/usr/include

    默认库安装路径是:/lib64

    此时我们运行:

     我们发现仍然是会报错的,但是已经不是找不到头文件了,而是链接错误,为什么呢?

    因为这个是我们自己配置的第三方库,所以我们必须的指定库名称,否则将会找不到库,当我们指定库名称时来试试:

     很显然已经成功运行了。

    其实这就是第三方库,非语言层面非操作系统层面给我们提供的库,我们自己下载的库一般都会下载到系统编译器默认的搜索路径下方便使用。

    总结:第三方库的使用

    • 要指定头文件和库文件。
    • 如果没有安装到系统gcc/g++默认的搜索路径下,用户必须指定选项来告知编译器:a:头文件在哪里b:库文件在哪里c:库文件名称
    • 将我们下载到的头文件和库文件拷贝考系统的默认路径下,需要带上库文件的名称来找到库文件。
    • 一般来说,普通用户将下载好的库安装到系统默认路径下都是需要sudo来提权的。

     4 制作动态库

    首先来说,制作动态库时生成目标文件要加上 -fPIC 选项,表示的是与位置无关码

    (position ignore code)至于为啥是这样文章末尾会给出解释。

    1. [grm@VM-8-12-centos owner]$ gcc -fPIC -c *.c
    2. [grm@VM-8-12-centos owner]$ ll
    3. total 24
    4. -rw-rw-r-- 1 grm grm 61 Apr 1 22:32 my_add.c
    5. -rw-rw-r-- 1 grm grm 40 Apr 1 22:32 my_add.h
    6. -rw-rw-r-- 1 grm grm 1240 Apr 2 10:53 my_add.o
    7. -rw-rw-r-- 1 grm grm 61 Apr 1 22:33 my_sub.c
    8. -rw-rw-r-- 1 grm grm 39 Apr 1 22:33 my_sub.h
    9. -rw-rw-r-- 1 grm grm 1240 Apr 2 10:53 my_sub.o

     这时我们打包就不用ar命令了,直接用gcc打包,只是要带上选项 -shared

    1. [grm@VM-8-12-centos owner]$ gcc -shared -o libmymath.so *.o
    2. [grm@VM-8-12-centos owner]$ ll
    3. total 32
    4. -rwxrwxr-x 1 grm grm 7952 Apr 2 10:56 libmymath.so
    5. -rw-rw-r-- 1 grm grm 61 Apr 1 22:32 my_add.c
    6. -rw-rw-r-- 1 grm grm 40 Apr 1 22:32 my_add.h
    7. -rw-rw-r-- 1 grm grm 1240 Apr 2 10:53 my_add.o
    8. -rw-rw-r-- 1 grm grm 61 Apr 1 22:33 my_sub.c
    9. -rw-rw-r-- 1 grm grm 39 Apr 1 22:33 my_sub.h
    10. -rw-rw-r-- 1 grm grm 1240 Apr 2 10:53 my_sub.o

    这也很好的解释了为啥云服务器默认都是动态库。

    为了规范性我们将*.h的文件放进了include目录下,然后将打包生成的动态库放进lib目录下,然后再打包:

     将包拷贝给otherUsr并且解压:

     我们按照之前实现静态库的方式来编译链接:

    1. [grm@VM-8-12-centos otherUsr]$ gcc -o mytest main.c -I include -L lib -l mymath
    2. [grm@VM-8-12-centos otherUsr]$ ll
    3. total 28
    4. drwxrwxr-x 2 grm grm 4096 Apr 2 10:59 include
    5. drwxrwxr-x 2 grm grm 4096 Apr 2 11:02 lib
    6. -rw-rw-r-- 1 grm grm 187 Apr 1 22:48 main.c
    7. -rwxrwxr-x 1 grm grm 8432 Apr 2 11:14 mytest
    8. -rw-rw-r-- 1 grm grm 2359 Apr 2 11:08 owner.tgz
    9. [grm@VM-8-12-centos otherUsr]$ ./mytest
    10. ./mytest: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory

    我们发现生成了可执行文件,但是运行时却发现找不到文件,这是为啥呀?我们不是已经指定了库的名称和路径了吗?为啥还是找不到呢?

    我们可以反过来思考一下:我们使用-L-l选项时是将库的路径和名称告诉了谁?

    是操作系统吗?显然不是,我们只是告诉了编译器,但是没有告诉操作系统,也就是操作系统找不到库在哪里了。那为啥静态库就可以呢?

    回想一下静态库的原理,静态库直接将用户导的库的二进制代码拷贝到可执行程序中,所以操作系统能够直接找到,但是动态库却不会。

    我们可以通过ldd命令查看一下:

    那么操作系统是如何查找动态库的呢?主要有以下3种方式:

    • 1 通过环境变量 LD_LIBRARY_PATH

    我们可以先查看该环境变量里面有什么?

    1. [grm@VM-8-12-centos otherUsr]$ echo $LD_LIBRARY_PATH
    2. :/home/grm/.VimForCpp/vim/bundle/YCM.so/el7.x86_64

    然后我们将库路径导入到该环境变量中:

    1. [grm@VM-8-12-centos otherUsr]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/grm/lesson14/otherUsr/lib
    2. [grm@VM-8-12-centos otherUsr]$ echo $LD_LIBRARY_PATH
    3. :/home/grm/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/grm/lesson14/otherUsr/lib

    不知道大家注意到没,我们导入环境变量时并没有具体到库的名称,而只是到了存放库的目录路径下,这是为了方便后面我们将其他库也导入该目录中也能够正确使用。

    此时我们通过ldd查看:

     我们运行起来试试:

     很显然能够成功。但是这样做有一个很大的问题:那就是当我们退出的时候配置的环境变量会自动销毁,所以这种方式只是一种临时方案。

    • 2 软链接方案

    前面我们介绍了软链接,这里通过软链接是一种比较好的方案:

    [grm@VM-8-12-centos otherUsr]$ sudo ln -s /home/grm/lesson14/otherUsr/lib/libmymath.so  /lib64/libmymath.so
    

    这种方式也能够让操作系统找到库:

     而且这种方式当我们没有删除软链接的时候是永久保存的,不想要了直接删除软链接即可。

    • 3 配置文件方案

    首先我们使用unlink命令解除软链接关系,然后ldd查看:

    这时已经没有了软链接,然后我们使用配置文件方案:

    先查看一下 /etc/ld.so.conf.d 目录下的内容:

     然后我们提权创建一个自己的文件,并向文件中写入我们库所在的路径:

    1. [grm@VM-8-12-centos otherUsr]$ sudo touch /etc/ld.so.conf.d/mystudy.conf
    2. [grm@VM-8-12-centos otherUsr]$ sudo vim /etc/ld.so.conf.d/mystudy.conf
    3. [grm@VM-8-12-centos otherUsr]$ cat /etc/ld.so.conf.d/mystudy.conf
    4. /home/grm/lesson14/otherUsr/lib

    用vim打开文件时一定要提权,否则可能无法保存。

    这时为了让配置的文件立即生效需要用 ldconfig 命令:

    1. [grm@VM-8-12-centos otherUsr]$ sudo ldconfig
    2. [grm@VM-8-12-centos otherUsr]$ ldd mytest
    3. linux-vdso.so.1 => (0x00007ffed5dfb000)
    4. libmymath.so => /home/grm/lesson14/otherUsr/lib/libmymath.so (0x00007f853583b000)
    5. libc.so.6 => /lib64/libc.so.6 (0x00007f853546d000)
    6. /lib64/ld-linux-x86-64.so.2 (0x00007f8535a3d000)
    7. [grm@VM-8-12-centos otherUsr]$ ./mytest
    8. 10 + 20 = 30
    9. 10 + 20 = -10

    这时便能够成功运行了。


    5 动静态库的理解

    5.1 静态库的理解

    我们知道,静态库是直接将二进制代码拷贝到可执行文件中的,那么当我们若干个进程使用相同的静态库时势必会重复拷贝多份相同的二进制代码,那么这样文件的大小肯定会变大,我们下载时会消耗更多的资源。(所以一般情况下我们不采取静态库)

    但是当我们生成了可执行文件后就算我们把静态库删除了也无所谓,因为我们已经将静态库的二进制代码拷贝到了可执行文件中,执行可执行文件依旧能够正常运行。

    5.2 动态库的理解

    动态库是不会将库的二进制代码直接拷贝到可执行文件的,而是链接时再去寻找,那么我们只需要load一份库的代码到内存中,通过页表映射,当我们执行时再到对应的进程地址空间寻找即可。这份库的代码在进程地址空间中对应着哪一个区呢?答案是共享区。在共享区中存放着库的二进制代码,但是这样会面临着一个问题:不同进程,运行程度是不同的,需要使用的第三方库是不同的,那么注定了每一个进程的共享空间中空闲位置是不确定的,如何找到不同进程对应的库呢?

    这时我们使用绝对编址的方法就已经行不通了,那么我们就要采取相对编址的方法,记录偏移量,通过偏移量来找到库的虚拟地址。这就说明此时加载库在共享区的时候随便你怎么加载,我们是通过偏移量来寻找的。

    制作动态库时生成目标文件要加上 -fPIC 选项,表示的是与位置无关码

    (position ignore code)就是这个原因。

  • 相关阅读:
    基于SSM的大学生创新创业平台竞赛管理子系统设计与实现
    信息化发展53
    SpringSecurity(十五)---OAuth2的运行机制(上)-OAuth2概念和授权码模式讲解
    flutter ios Exception : No Impeller Context is Available
    集合-set系列集合
    Golang string 常用方法
    音视频开发—V4L2介绍,FFmpeg 打开摄像头输出yuv文件
    华夏基金:基金行业数字化转型实践成果分享
    SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.10 jetcache 本地缓存方案
    【NOI模拟赛】伊莉斯elis(贪心,模拟)
  • 原文地址:https://blog.csdn.net/m0_68872612/article/details/129903483