首页 > 其他分享 >静态库与动态库

静态库与动态库

时间:2024-05-27 13:00:56浏览次数:21  
标签:文件 静态 int so 动态 加载

文章目录

参考文章

一、什么是库

​ 库是写好的,现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。

​ 本质上来说,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库动态库。linux下的静态库为.a结尾的文件,动态库以.so结尾的文件;windows下的静态库为以.lib为结尾的文件,动态库为以.dll为结尾的文件。

​ 所谓静态、动态是指链接。静态库、动态库区别来自链接阶段如何处理库,链接成可执行程序。

# 二、静态库

1.命名规则

在Linux中静态库由程序ar生成,现在静态库已经不像之前那么普遍了,这主要是由于程序都在使用动态库。关于静态库的命名规则如下:

  • 在Linux中静态库以lib作为前缀, 以.a作为后缀中间是库的名字自己指定即可, 即: libxxx.a

  • 在Windows中静态库一般以lib作为前缀, 以lib作为后缀, 中间是库的名字需要自己指定, 即: libxxx.lib

2.Linux下生成静态库的步骤

生成静态库,需要先对源文件进行汇编操作 (使用参数 -c) 得到二进制格式的目标文件 (.o 格式), 然后在通过 ar工具将目标文件打包就可以得到静态库文件了 (libxxx.a)。

生成静态链接库的具体步骤:
  1. 将源文件进行汇编, 得到 .o 文件

    # 执行如下操作, 默认生成二进制的.o文件
    # 需要使用参数 -c,该参数位置没有要求
    $ gcc -c 源文件(*.c) 
    
  2. 将得到的 .o 进行打包, 得到静态库

    $ar -rcs 静态库的名字(libxxx.a) 目标文件(*.o)
    

    使用ar工具创建静态库的时候需要三个参数:

    • 参数c:创建一个库,不管库是否存在,都将创建。
    • 参数s:创建目标文件索引,这在创建较大的库时能节约时间。
    • 参数r:在库中插入模块(替换)。默认新的成员添加在库的结尾处,如果模块名已经在库中存在,则替换同名的模块。
  3. 发布静态库

    1. 提供头文件 **.h
    2. 提供制作出来的静态库 libxxx.a
    

3.静态库的使用

当我们得到了一个可用的静态库之后, 需要将其放到一个目录中(头文件和静态库文件放在同一个文件夹下); 然后根据得到的头文件编写测试代码, 对静态库中的函数进行调用;最后编译测试程序, 得到可执行文件。

在编译的时将静态库的路径和名字都指定出来

# -L: 指定库所在的目录(相对或者绝对路径),与参数之间可以不加空格
# -l: 指定库的名字, 需要掐头(lib)去尾(.a) 剩下的才是需要的静态库的名字,与参数之间可以不加空格
gcc main.c -o app -L库所在目录 -l静态库名

4.静态库制作举例

1.源码

在某个目录中有如下的源文件, 用来实现一个简单的计算器:

# 目录结构 add.c div.c mult.c sub.c -> 算法的源文件, 函数声明在头文件 head.h
.
├── add.c
├── div.c
├── include
│   └── head.h
├── mult.c
└── sub.c

算法文件add.c div.c mult.c sub.c

add.c

#include <stdio.h>
#include "head.h"

int add(int a, int b)
{
    return a+b;
}

div.c

#include <stdio.h>
#include "head.h"

int subtract(int a, int b)
{
    return a-b;
}

mult.c

#include <stdio.h>
#include "head.h"

int multiply(int a, int b)
{
    return a*b;
}

sub.c

#include <stdio.h>
#include "head.h"

double divide(int a, int b)
{
    return (double)a/b;
}

头文件head.h

#ifndef _HEAD_H
#define _HEAD_H
// 加法
int add(int a, int b);
// 减法
int subtract(int a, int b);
// 乘法
int multiply(int a, int b);
// 除法
double divide(int a, int b);
#endif

2.静态库的制作

# 将源文件进行汇编, 得到 .o 文件
$ gcc -c add.c div.c mult.c sub.c -I ./include/
# 将得到的 .o 进行打包, 得到静态库
$ ar rcs libcalc.a *.o
# 发布静态库
# 1. 提供头文件 **.h
# 2. 提供制作出来的静态库 libxxx.a

3.静态库的使用

测试文件main.c,main.c中是对接口的测试程序, 制作库的时候不需要将 main.c 算进去,需要提高头文件和静态库文件

目录结构

.
├── main.c
├── libcalc.a
├── include
   └── head.h

main.c

#include <stdio.h>
#include "head.h"

int main()
{
    int a = 20;
    int b = 12;
    printf("a = %d, b = %d\n", a, b);
    printf("a + b = %d\n", add(a, b));
    printf("a - b = %d\n", subtract(a, b));
    printf("a * b = %d\n", multiply(a, b));
    printf("a / b = %f\n", divide(a, b));
    return 0;
}

使用静态库,编译测试文件

$ gcc main.c -o app -L ./ -l calc -I./include/
$ ./app

三、动态库

​ 动态链接库是程序运行时加载的库,当动态链接库正确部署之后,运行的多个程序可以使用同一个加载到内存中的动态库,因此在Linux中动态链接库也可称之为共享库。

​ 动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址使用的是相对地址(静态库中使用的是绝对地址),其真实地址是在应用程序加载动态库时形成的。

1.命名规则

关于动态库的命名规则如下:

  • 在Linux中动态库以lib作为前缀, 以.so作为后缀, 中间是库的名字自己指定即可, 即: libxxx.so
  • 在Windows中动态库一般以lib作为前缀, 以dll作为后缀, 中间是库的名字需要自己指定, 即: libxxx.dll

2.Linux下生成动态库的步骤

生成动态链接库的具体步骤:

1.将源文件进行汇编操作

# 需要使用参数 -c, 还需要添加额外参数 -fpic / -fPIC
# -fPIC 或 -fpic 参数的作用是使得 gcc 生成的代码是与位置无关的,也就是使用相对位置
# 得到若干个 .o文件
$ gcc -c 源文件(*.c) -fpic

2.将得到的.o文件打包成动态库

# 使用gcc, 使用参数 -shared 指定生成动态库(位置没有要求)
# -shared参数的作用是告诉gcc编译器生成一个动态链接库
$ gcc -shared 与位置无关的目标文件(*.o) -o 动态库(libxxx.so)

3.发布动态库和头文件

# 发布动态库
 	1. 提供头文件: xxx.h
 	2. 提供动态库: libxxx.so

3.动态库的使用

$ gcc main.c -o app -L ./ -l calc -I./include/
$ ./app
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory 
# 出现报错参考3.解决动态库无法加载的问题

4.动态库的制作举例

目录结构

.
├── add.c
├── div.c
├── include
│   └── head.h
├── mult.c
└── sub.c

1.动态库的制作

#1.将源文件进行汇编操作
$ gcc *.c -c -fpic -I ./include/

#2.将得到的.o文件打包成动态库
$ gcc -shared *.o -o libcalc.so 

#3. 发布库文件和头文件
	1. head.h
	2. libcalc.so

2.动态库的使用

​ 当我们得到了一个可用的动态库之后, 需要将其放到一个目录中, 然后根据得到的头文件编写测试代码, 对动态库中的函数进行调用。

目录结构

.
├── head.h          ==> 函数声明
├── libcalc.so      ==> 函数定义
└── main.c          ==> 函数测试

编译测试程序

# 在编译的时候指定动态库相关的信息: 库的路径 -L, 库的名字 -l
$ gcc main.c -o app -L./ -lcalc

# 执行生成的可执行程序, 错误提示 ==> 可执行程序执行的时候找不到动态库
$ ./app 
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

gcc通过指定的动态库信息生成了可执行程序, 但是可执行程序运行却提示无法加载到动态库。

3.解决动态库无法加载的问题

1.库的工作原理
链接的三种方式:

1.静态链接:在程序运行之前先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块),之后不再拆开。

2.装入时动态链接:将各目标模块装入内存时,边装入边链接的链接方式。

3.运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。

静态库不需要加载,在测试程序编译的最后一个阶段也就是链接阶段,提供的静态库会被打包到可执行程序中。当可执行程序被执行,静态库中的代码也会一并被加载到内存中,因此不会出现静态库找不到无法被加载的问题。由于静态库是将代码直接拷贝到C程序的代码区中,因此内存中的代码和数据可能会存在多份,造成内存空间浪费。

动态库fPIC形成位置无关码,采用相对编址的方式,在程序链接时将对应库中的偏移量添加到程序中,库函数在程序运行时加载进来,经过页表,把库映射到虚拟地址空间后(共享区),库就具有了起始地址。通过起始地址和偏移地址,就可以找到要调用的库函数。

  • 在程序编译的最后一个阶段也就是链接阶段:

    • 在gcc命令中虽然指定了库路径(使用参数 -L ), 但是这个路径并没有记录到可执行程序中,只是检查了这个路径下的库文件是否存在。
    • 同样对应的动态库文件也没有被打包到可执行程序中,只是在可执行程序中记录了库的名字。
  • 可执行程序被执行起来之后:

    • 程序执行的时候会先检测需要的动态库是否可以被加载,加载不到就会提示上边的错误信息

      ./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

    • 当动态库中的函数在程序中被调用了, 这个时候动态库才加载到内存,如果不被调用就不加载

    • 动态库的检测和内存加载操作都是由动态连接器来完成的

​ 动态库相对于静态库更节省内存,静态库由多个程序使用相同的库函数,加载到内存中就会导致内存中有多份重复的库函数代码,而动态库则是多个程序共用一份动态库,不会导致出现重复的库函数代码,就节省了内存空间。

2.动态链接器

​ 动态链接器是一个独立于应用程序的进程, 属于操作系统, 当用户的程序需要加载动态库的时候动态连接器就开始工作了,很显然动态连接器根本就不知道用户通过 gcc 编译程序的时候通过参数 -L指定的路径。

那么动态链接器是如何搜索某一个动态库的呢?在它内部有一个默认的搜索顺序,按照优先级从高到低的顺序分别是:

  1. 可执行文件内部的 DT_RPATH 段
  2. 系统的环境变量 LD_LIBRARY_PATH
  3. 系统动态库的缓存文件 /etc/ld.so.cache
  4. 存储动态库/静态库的系统目录 /lib/, /usr/lib等

按照以上四个顺序, 依次搜索, 找到之后结束遍历, 最终还是没找到, 动态连接器就会提示动态库找不到的错误信息。

3.解决方案

​ 可执行程序生成之后, 根据动态链接器的搜索路径, 我们可以提供三种解决方案,我们只需要将动态库的路径放到对应的环境变量或者系统配置文件中,同样也可以将动态库拷贝到系统库目录(或者是将动态库的软链接文件放到这些系统库目录中)。

  • 方案1: 将库路径添加到环境变量 LD_LIBRARY_PATH 中

    1. 找到相关的配置文件

      • 用户级别: ~/.bashrc —> 设置对当前用户有效
      • 系统级别: /etc/profile —> 设置对所有用户有效
    2. 使用 vim 打开配置文件, 在文件最后添加这样一句话

      # 自己把路径写进去就行了
      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库的绝对路径
      
    3. 让修改的配置文件生效

      • 修改了用户级别的配置文件, 关闭当前终端, 打开一个新的终端配置就生效了

      • 修改了系统级别的配置文件, 注销或关闭系统, 再开机配置就生效了

      • 不想执行上边的操作, 可以执行一个命令让配置重新被加载

        # 修改的是哪一个就执行对应的那个命令
        # source 可以简写为一个 . , 作用是让文件内容被重新加载
        $ source ~/.bashrc          (. ~/.bashrc)
        $ source /etc/profile       (. /etc/profile)
        
  • 方案2: 更新 /etc/ld.so.cache 文件

    1. 找到动态库所在的绝对路径(不包括库的名字)比如:/home/robin/Library/

    2. 使用vim 修改 /etc/ld.so.conf 这个文件, 将上边的路径添加到文件中(独自占一行)

      # 1. 打开文件
      $ sudo vim /etc/ld.so.conf
      
      # 2. 添加动态库路径, 并保存退出
      
    3. 更新 /etc/ld.so.conf中的数据到 /etc/ld.so.cache

      # 必须使用管理员权限执行这个命令
      $ sudo ldconfig   
      
  • 方案3: 拷贝动态库文件到系统库目录 /lib/ 或者 /usr/lib 中 (或者将库的软链接文件放进去)

    # 库拷贝
    $ sudo cp /xxx/xxx/libxxx.so /usr/lib
    
    # 创建软连接
    $ sudo ln -s /xxx/xxx/libxxx.so /usr/lib/libxxx.so
    

四、静态库与动态库的优缺点

1.静态库

优点:

  • 静态库被打包到应用程序中加载速度快
  • 发布程序无需提供静态库,移植方便

缺点:

  • 相同的库文件数据可能在内存中被加载多份, 消耗系统资源,浪费内存

  • 库文件更新需要重新编译项目文件, 生成新的可执行程序, 浪费时间

2.动态库

优点:

  • 可实现不同进程间的资源共享
  • 动态库升级简单, 只需要替换库文件, 无需重新编译应用程序
  • 程序猿可以控制何时加载动态库, 不调用库函数动态库不会被加载

缺点:

  • 加载速度比静态库慢, 以现在计算机的性能可以忽略
  • 发布程序需要提供依赖的动态库

标签:文件,静态,int,so,动态,加载
From: https://blog.csdn.net/weixin_56438859/article/details/139213052

相关文章

  • 传感器的静态特性
    传感器的静态特性是指传感器在稳态(输入量为常量或变化极慢时)输入信号作用下,传感器输出与输入信号之间的关系。这种关系一般用曲线、数学表达式或表格来表示。传感器的静态特性是传感器的基本特性之一,其描述了传感器在不考虑迟滞、蠕变和不稳定性等因素时的输入输出关系。衡......
  • Excel工作表单元格单击选中事件,VBA动态数值排序
    Excel工作表单元格单击选中事件,VBA动态数值排序(WX公众号:Excel潘谆白说VBA)文章目录前言一、运行效果二、代码前言面对每月的消费账单,面对月底待还的信用卡或花呗,面对不足三位数的余额,你是否怀疑过账单自己的消费。你是否因此开始记账,每个月记流水,想知道当月......
  • 动态执行JS-把字符串当做代码去执行
    使用eval将字符串当做代码来执行functionzhiXing(strCode){eval(strCode)}zhiXing("console.log('hello')")在控制台会输出:helloeval的简单介绍1,eval是同步代码2,eval()执行代码时,读取变量是当前作用域;他会先去找当前作用域中有没有这个值;如果有就获取,如果......
  • phpstorm进行动态调试
    php远程调试配置xdebug配置先下载xdebug,看phpinfo内容选版本ctrl+a全选phpinfo后复制去识别即可(Xdebug:Support—TailoredInstallationInstructions)下载后,放入php\ext目录里面,一般教程都是直接去php.ini直接加信息,但其实可以先不急,去phpstudy先选上扩展勾上xdebug,......
  • 【设计模式】代理模式——详解静态代理&动态代理
    内容由B站UP主动力节点产出,本文仅作为学习笔记代理模式定义:为对象提供一种代理,以控制这个对象的访问操作。代理对象和目标对象之间起到中介的作用。作用:保护目标对象和增强目标对象举例说明,以一个订单对象为例:classOrder{ privateStringinfo; privateStringus......
  • Phpstorm动态调试
    php远程调试配置xdebug配置先下载xdebug,看phpinfo内容选版本ctrl+a全选phpinfo后复制去识别即可(Xdebug:Support—TailoredInstallationInstructions)下载后,放入php\ext目录里面,一般教程都是直接去php.ini直接加信息,但其实可以先不急,去phpstudy先选上扩展勾上xdebug,......
  • Debian/Linux网络配置全面指南:从静态IP到DNS设置
    在Debian/Linux上配置网络涉及多个步骤,包括设置静态IP地址、配置网关和DNS服务器等。以下是一个详细的教程,指导你如何在Debian/Linux系统上进行网络配置。1.编辑网络接口配置文件在Debian/Linux上,网络接口的配置文件通常位于/etc/network/interfaces。首先,以超级用户身份......
  • 【Linux】为 VMware 的 Linux 系统(CentOS 7)设置静态IP地址
    文章目录准备工作查看子网掩码和网关IP确认准备设置的虚拟机端口没有被占用调整设置编辑配置文件配置文件说明完成配置,准备测试使用命令终端连接服务器我是一名立志把细节说清楚的博主,欢迎【关注】......
  • 线性动态规划
    《算法设计与分析》期末复习导弹拦截https://www.luogu.com.cn/problem/P1020定义状态\(f(i)\)为\(1~i\)区间内的最大导弹拦截数量有状态转移\[f(j)=max{f(i)}+(a[j]<=a[i])\]直接赤裸裸的做状态转移,时间复杂度预计在\(O(n^2)\),空间复杂度在\(O(n)\)考虑......
  • java实现一个动态监控系统,监控接口请求超时的趋势
    目录整体思路案例实现1.数据收集2.数据聚合3.趋势分析4.异常检测5.异常处理定时任务整体思路理想情况下,你可以实现一个简单的动态监控算法来检测渠道请求的响应时间趋势,并在发现频繁超时的情况下进行处理。以下是一个可能的算法框架:数据收集:首先,你需要收集......