【Hello Linux】动态库和静态库

作者:@小萌新
专栏:@Linux
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:简单介绍下动态库和静态库的概念

引出

我们首先写出下面的一段很简单的c语言代码

  1 #include <stdio.h>
  2 
  3 int main()
  4 {
    
    
  5   printf("hello world\n");                             
  6   return 0;
  7 }

这段代码我们在一开始学习c语言的时候就会写了 它的运行结果我们当然也很清楚 会向显示器打印 hello world

在这里插入图片描述
再回到上面的那段代码中 我们有没有想过这样一个问题

为什么我们调用 printf函数 就能够向屏幕打印信息呢?

通过前面基础IO和操作系统部分的学习

基础IO

操作系统

我们知道c语言其实是用户层的函数

它的下面有系统调用接口 系统调用接口经过封装之后变成为了我们的printf函数

而系统调用接口接受这个指令之后操作系统便指挥显示器的驱动向屏幕打印函数内的信息

在这里插入图片描述
也就是说我们在语言层面所直接使用的函数其实全部是别人封装好的(lib)

而我们上面的那段代码之所以可以运行就是因为gcc编译器在生成可执行文件的时候将c语言标准库连接了进来

初识动静态库

我们怎么查看自己使用了哪些库呢?

在linux操作系统下 我们可以使用 ldd + 可执行文件名 来查看一个文件运行所依赖的库文件

在这里插入图片描述

这里面的 /lib64/lic.so.6 其实就是我们可执行程序所依赖的库文件

我们使用ls指令查看该库文件 发现它是一个文件的软连接

紧接着我们使用ll指令查看到该软连接的源文件

在这里插入图片描述

知道了该软连接的源文件之后我们可以使用file指令来查看它具体的文件类型

在这里插入图片描述

我们可以看到 它是一个共享的目标文件库 (shared object)被用作动态连接 (dynamically linked)

怎么辨认linux下的动静态库?

  • 在linux系统中动态库一般以.so为后缀 静态库一般以.a为后缀

顺便提一嘴

  • 在windows系统中动态库一般以.dll为后缀 静态库一般以.lib为后缀

库的名字是什么呢?

我们看到的库的源文件名字是 libc-2.17.so

我们去掉前面它的标识符lib 去掉后面的后缀.so 之后剩下的就是它的名字

即库的名字为 c-2.17

在默认情况下 我们编译程序都是使用动态连接的

在这里插入图片描述

如果我们想要进行静态链接则需要在后面加上一个-static选项

在这里插入图片描述

进行静态链接之后我们可以明显发现可执行文件的大小比动态链接的可执行文件大小大得多

这是因为使用静态链接编译可执行文件的话编译器会将整个库打包到可执行文件当中

我们可以使用 ldd + 静态链接后的文件名 来证明这一点

在这里插入图片描述

此外我们还可以使用file指令来查看它们是什么链接方式

在这里插入图片描述

动静态库的基本原理

动静态库的本质是可执行程序的“半成品”

我们都知道 语言从源文件变为可执行文件要分为下面四步

  1. 预处理 (去除注释 头文件展开 宏替换 条件编译)变成 .i 文件
  2. 编译 (将c/c++ 代码翻译成汇编代码)变成 .s 文件
  3. 汇编 (将汇编代码转化为二进制语言)变成 .o 文件
  4. 链接 (将汇编过程产生的二进制文件进行连接) 变成可执行文件

比如说 如果我们想要将五个源文件变为一个可执行文件的话我们就应该这么做

首先将这五个文件全部汇编变成.o目标文件

之后使用gcc编译器将这五个目标文件链接

在这里插入图片描述

而如果我们在别的项目中也需要用到这些二进制文件的话只需要复制一份过去之后用gcc编译器进行连接就可以

在这里插入图片描述

如果所有的项目几乎都要频繁的用到这几个目标文件呢?

那么我们实际上就可以对于这几个文件进行打包 放到环境配置中

每次要使用的时候自动到这个环境配置里面找

这样子打包后的文件就叫做库文件

在这里插入图片描述

所以说 各种动静态库的本质实际上就是一堆的目标文件(.o文件)

它们当中不包含main函数而是包含了大量的方法供使用者调用

这就是动静态库的原理 所以我们可以说动静态库的本质是可执行程序的“半成品”

动静态库的各自特征

我们将动静态库和可执行文件的概念映射到现实生活中

在这里插入图片描述

你在宿舍中写作业 动态库就像是网吧中的电脑

当你遇到不会的题目的时候 你就会去网吧查资料

而静态库就像是你在宿舍中买的电脑

当你遇到不会的题目的时候 你就会使用自己的电脑去查资料

静态库

静态库链接就是程序在编译连接的将静态库中的代码下载到可执行文件当中

所以说我们使用静态库生成的可执行文件一般体积比较大

自然可执行程序运行的时候也不需要依赖外部库文件了

  • 优点

使用静态库链接之后 文件不需要依赖外部库文件也可以执行

  • 缺点

使用静态库链接的库文件体积较大 占用空间多 且多个静态链接的可执行文件中可能会有大量的重复代码

动态库

动态库链接是程序在运行的时候才去链接相应的动态库代码 多个程序共享使用库的代码

在可执行文件开始运行前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存当中 这个过程被称为动态链接

为了节省空间 操作系统利用虚拟内存机制 将中间的一片共享区指向了物理内存中同一块动态库区域

在这里插入图片描述

  • 优点

节省磁盘空间 避免重复代码

  • 缺点

依赖库文件运行 如果缺少库文件则无法运行

静态库的打包和使用

我们下面使用静态库打包和使用的例子让大家更加深入的理解静态库

我们使用下面的四个文件来演示

add.h

  1 #pragma once 
  2                                                   
  3 extern int myadd(int x , int y);

add.c

  1 #include "add.h"  
  2   
  3 int myadd(int x , int y)  
  4 {
    
      
  5   return x + y;                                   
  6 } 

sub.h

  1 #pragma once 
  2 
  3 extern int mysub(int x , int y);  

sub.c

  1 #include "sub.h"
  2 
  3 int mysub(int x , int y)
  4 {
    
    
  5   return x-y;                                     
  6 }

打包

第一步 生成二进制文件

在这里插入图片描述

第二步 将所有目标文件打包为静态库

ar命令是gnu的归档工具 常用于将目标文件打包为静态库

下面是命令选项

  • -r(replace):若静态库文件当中的目标文件有更新 则用新的目标文件替换旧的目标文件
  • -c(create):建立静态库文件
  • -t:列出静态库中的文件
  • -v(verbose):显示详细的信息

我们可以使用下面的指令将一个或多个目标文件打包为静态库

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

在这里插入图片描述

我们可以使用下面的指令查看一个静态库中的文件

ar -tv libst.a

在这里插入图片描述

第三步 将头文件和生成的动态库组织起来

与我们使用时一样

我们可以将所有的头文件放到 /include 文件夹中

将所有的静态库文件放到 /lib 文件夹中

接着将这两个目录放到 /mylib

这样子我们就可以将我们的mylib给别人使用了

在这里插入图片描述

使用

我们首先写出一个源文件 main.c

    1 #include <stdio.h>
E>  2 #include <add.h>
E>  3 #include <sub.h>
    4 
    5 
    6 
    7 int main()
    8 {
    
    
    9   int x = 10;                                                                                            
   10   int y = 20;
E> 11   int z1 = myadd(x , y);
E> 12   int z2 = mysub(x , y);
   13   printf ("myadd :: %d\n" , z1);
   14   printf ("mysub :: %d\n" , z2);
   15   return 0;
   16 

我们发现 这个源文件中其实报了四个错误 这是因为我们c语言程序在查到头文件的时候只会查找同级的头文件还有系统配置的头文件

在这里插入图片描述
而我们当前目录下只有两个文件 main.c 和 mylib

不过我们不必担心 编译之后的程序肯定是可以正常运行的

使用方式一: 使用选项

gcc编译时我们需要使用的三个选项如下

  • -I:指定头文件搜索路径
  • -L:指定库文件搜索路径
  • -l:指明需要链接库文件路径下的哪一个库

所以说我们只需要敲出下面的指令他就可以

gcc -o main main.c -I ./mylib/include/ -L ./mylib/lib/ -l st

这段指令的意思是使用main.c源程序编译 指定某个头文件 指定某个库文件 指定st这个库
编译生成一个名为main的可执行文件

在这里插入图片描述

这个时候我们就可以运行我们的程序了

使用方式二: 把头文件和库文件拷贝到系统路径下

编译器之所以能够找到c的头文件和库文件是因为它们被放在默认的路径中

当我们开始编译的时候编译器就会默认去那里寻找

而头文件的默认路径在这里

   /usr/include/

而库文件的默认路径在这里

  /lib64/

所以说我们只需要将自己的头文件和库文件拷贝到这两个地方就可以了

我们使用下面两个指令来拷贝

  cp ./mylib/include/* /usr/include/
  cp ./mylib/lib/libst.a /lib64/

之后我们再看main文件代码

在这里插入图片描述

我们发现这次代码并没有报错

在这里插入图片描述

但是我们这里发现gcc编译的时候报错了 这是因为gcc编译的时候默认找的是c库 而我们添加的库并不是c库

所以说我们编译的时候还需要指定依赖的库文件

在这里插入图片描述

注意: 我十分不建议将自己写的库文件拷贝到系统路径中 这样子会污染原本的库文件

动态库的打包和使用

我们还是使用这四个库文件来演示动态库的打包和使用

在这里插入图片描述

打包

第一步 生成对应的目标文件

让所有源文件生成对应的目标文件

动态库与静态库汇编指令中不同的是 动态库的汇编指令中需要加上-fPIC选项

-fPIC选项的意义是生成和位置无关码

它是一个很复杂的概念 涉及到很多的专业知识 这里就不过多赘述 对于它我们只需要了解两点

  1. -fPIC作用于编译阶段 告诉编译器产生与位置无关的代码 之后代码被加载到内存的任何位置都可以运行
  2. -fPIC选项并不是必须要加的 如果不加动态库必须要在加载到用户程序的地址空间时重定向所有表目

在这里插入图片描述

第二步 打包动态库

与生成静态库时使用ar命令打包不同 我们只需要使用gcc的-shared选项即可

指令如下

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

在这里插入图片描述

第三步 将头文件和生成的动态库组织起来

和生成的静态库一样

我们可以将所有的头文件放到 /include 文件夹中

将所有的静态库文件放到 /lib 文件夹中

接着将这两个目录放到 /mylib

这样子我们就可以将我们的mylib给别人使用了

在这里插入图片描述

在这里插入图片描述

使用

我们还是使用静态库的源文件main.c来演示动态库的使用

    1 #include <stdio.h>
E>  2 #include <add.h>
E>  3 #include <sub.h>
    4 
    5 
    6 
    7 int main()
    8 {
    
    
    9   int x = 10;
   10   int y = 20;
E> 11   int z1 = myadd(x , y);
E> 12   int z2 = mysub(x , y);
   13   printf ("myadd :: %d\n" , z1);
   14   printf ("mysub :: %d\n" , z2);
   15   return 0;                                                            
   16 }

动态库的编译方法和静态库的编译方法一模一样

我们可以使用指令或者是将库文件和头文件放到系统目录中

我们这里使用指令为例

我们敲出下面的指令就可以编译

在这里插入图片描述

我们发现这样子就生成了我们的可执行程序了

但是和静态库的使用不一样 使用动态库的文件在成为可执行文件的时候还不能直接运行

在这里插入图片描述

我们再使用ldd指令 查看这个可执行文件所依赖的动态库

在这里插入图片描述

我们发现系统找不到这个动态库在哪里

这是因为 我们使用-I,-L,-l这三个选项都是在编译期间告诉编译器我们使用的头文件和库文件在哪里以及是谁 但是当生成的可执行程序生成后就与编译器没有关系了

所以说如果我们想要执行这个程序 还需要想办法告诉操作系统这个库文件的地址

这里有三种方式可以做到

方式一 拷贝到系统路径下

我们直接将该动态库拷贝到系统的库文件目录下 这样子操作系统就能够找到了

指令如下

cp ./mlib/dylib/libdy.so /lib64

这样子就可以顺利运行了
在这里插入图片描述

方式二 更改环境变量

LD_LIBRARY_PATH是程序运行动态查找库时所要搜索的路径 我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH环境变量当中即可

使用指令如下

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/shy/linux/lesson11~20/lesson13/code4/mlib/dylib/

之后我们再查询a.out的动态库 可以发现

在这里插入图片描述

我们可以发现动态库的路径变为了我们设置的路径

在这里插入图片描述

当然现在也可以继续运行了

方式三 配置/etc/ld.so.conf.d/

我们可以通过配置/etc/ld.so.conf.d/的方式解决该问题

首先我们自己需要写一个以.conf后缀的文件

将我们库文件所在的路径放到这个文件当中

在这里插入图片描述
之后将这个配置文件放到 /etc/ld.so.conf.d/ 目录中

之后使用指令更新配置文件

   ldconfig

这样子我们就可以运行我们的文件了

在这里插入图片描述

投票

猜你喜欢

转载自blog.csdn.net/meihaoshy/article/details/129496437
今日推荐