【Linux学习】深入理解动态库与静态库

目录

十三.动态库与静态库

        13.1 认识动静态库

        13.2 深入理解动静态库

        什么是库?

        编译链接过程

        动静态库的基本原理

        13.3 静态库

        静态库的打包:

        静态库的使用:

        13.4 动态库

        动态库的打包:

        动态库的使用:

        13.5 动态库与静态库怎么选? 


十三.动态库与静态库

        13.1 认识动静态库

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

ldd命令可以查看一个可执行程序依赖的共享库

ldd 可执行程序名

gcc/g++编译器默认都是动态链接的,若想进行静态链接,可以携带 -static 选项:

# 动态链接
gcc -o my_program my_program.c # 静态链接
gcc -o my_program_static my_program.c -static

        13.2 深入理解动静态库

什么是库?

在计算机编程中,库(Library)是一组已经编写好的代码、函数和例程的集合,可以供程序员在自己的程序中重复使用。这些库中的代码通常提供了一些通用的功能,例如数据结构、算法、输入/输出操作等,以便开发人员能够更容易地实现特定的任务,而不必从头开始编写所有的代码

编译链接过程

我们都知道C/C++程序从源代码到可执行程序会经历下面四个步骤:

  • 预处理 (Preprocessing):
    在这一阶段,预处理器会对源文件进行处理,展开头文件(#include指令),执行宏替换,去除注释,处理条件编译(#if、#ifdef等),生成一个被称为预处理后文件(通常具有.i扩展名)。
  • 编译 (Compiling):
    编译器接收预处理后的文件,进行词法分析、语法分析和语义分析,生成相应的汇编代码。这个阶段的输出是一个汇编文件(通常具有.s扩展名)。
  • 汇编 (Assembling):
    汇编器接收编译生成的汇编代码,将其转换为机器语言(二进制代码)并生成目标文件(通常是.o文件)。目标文件包含与源文件对应的机器代码。
  • 链接 (Linking):
    链接器接收一个或多个目标文件,以及可能的库文件,将它们组合在一起生成最终的可执行文件。链接的过程包括符号解析、地址绑定和重定位等步骤,最终生成可执行文件。

动静态库的基本原理

而动静态库的区别其实主要体现在程序链接 (Linking)阶段

静态库的链接阶段:

  • 完全合并到可执行文件: 在链接阶段,静态库的目标文件会被完全合并到最终的可执行文件中。

  • 可执行文件独立: 最终生成的可执行文件包含了程序的所有代码和数据,独立于外部库文件。

  • 链接时解析符号: 编译器在链接时会将程序中引用的符号与静态库中的符号进行解析和匹配,将所有符号解析为具体的地址。

  • 生成独立的可执行文件: 链接阶段生成的可执行文件是一个独立的二进制文件,不需要依赖外部库文件。

动态库的链接阶段:

  • 引用和延迟加载: 在链接阶段,编译器并不将动态库的目标文件合并到可执行文件中。相反,它在可执行文件中留下对动态库的引用。

  • 生成未定义符号: 编译器生成一个包含未定义符号引用的可执行文件,这些符号在运行时将由动态链接器解析。

  • 符号解析延迟到运行时: 实际的链接和加载是在程序运行时由操作系统的动态链接器完成的。动态链接器负责加载和链接动态库。

  • 生成依赖于动态库的可执行文件: 可执行文件包含有关如何在运行时加载和链接动态库的信息,但并不包含动态库的实际代码。

总体而言,静态库在链接阶段就将库的代码完全合并到可执行文件中,而动态库的链接是延迟到运行时进行的。这是为什么动态库能够实现共享和动态加载的原因。


光说不练假把式,下面我们在实操中进一步体会!

        13.3 静态库

首先,创建一个项目目录,例如 calculator_project,在其中创建以下文件:

创建文件结构: 

calculator_project/
|-- src/
|   |-- add.c
|   |-- multiply.c
|   |-- main.c
|-- include/
|   |-- calculator.h
|-- lib/

静态库的打包:

编写源文件:

编写 add.c:

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

编写 multiply.c:

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

编写头文件:

编写 calculator.h:

// calculator.h
#ifndef CALCULATOR_H
#define CALCULATOR_Hint add(int a, int b);
int multiply(int a, int b);#endif

编译源文件 :

这时候回到我们的回到calculator_project目录,使用以下命令编译源文件生成目标文件:

gcc -c src/add.c -o lib/add.o
gcc -c src/multiply.c -o lib/multiply.o

这将分别编译 add.c、multiply.c 并将生成的目标文件保存在 lib 目录中

创建静态库: 

我们进入lib目录,使用以下命令创建静态库 libcalculator.a:

ar -rcs libcalculator.a add.o multiply.o

ar 是GNU下一个用于创建和操作静态库(archive)的工具,而 rcs 是 ar 工具的一组选项,用于创建、替换或显示归档文件。
具体而言,ar rcs 命令的含义是:

-r(replace):用于将指定文件插入到归档文件中。如果文件已经存在于归档中,则替换掉原有的文件。
-c (create): 创建归档文件。如果归档文件不存在,则创建一个新的归档文件。
-s (index symbol table): 创建索引。创建归档文件的索引,这样可以更快地查找文件。

综合起来,ar rcs 命令通常用于创建或更新静态库的归档文件,将新的目标文件插入到归档中,并为归档文件创建索引。

静态库的使用:

 编写 main.c:

// main.c
#include <stdio.h>
#include "calculator.h"int main() {int result_add = add(5, 3);int result_multiply = multiply(4, 2);printf("Addition result: %d\n", result_add);printf("Multiplication result: %d\n", result_multiply);return 0;
}

编译源文件:

在src目录,使用以下命令编译源文件生成目标文件:

gcc -c main.c -o ../lib/main.o -I ../include

编译可执行文件:

使用以下命令编译可执行文件:

gcc lib/main.o -o calculator -L lib -lcalculator

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

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

运行程序:

运行生成的可执行文件:

        13.4 动态库

关于动态库,我们会基于静态库的实现来进一步来讲解

动态库的打包:

编译源文件 :

这时候回到我们的回到calculator_project目录,使用以下命令编译源文件生成目标文件:

gcc -c -fPIC src/add.c -o lib/add.o
gcc -c -fPIC src/multiply.c -o lib/multiply.o

-fpic 是 GCC 编译器的一个选项,用于生成位置无关代码(Position Independent Code,PIC)。位置无关代码是一种编译生成的代码,可以在内存中的任何位置执行而无需修改。这对于动态链接库(共享库)非常重要,因为共享库可以加载到内存的任何位置。

具体来说,-fpic 选项的作用包括:

  • 生成相对地址: 使用 -fpic 选项编译生成的代码中,引用地址都是相对地址而不是绝对地址。这使得代码可以在不同的内存位置正确执行。
  • 用于共享库: 这个选项通常用于编译动态库,确保生成的库是位置无关的。

如果在创建动态库时不加 -fPIC 选项,会导致生成的目标文件包含绝对地址的引用。这样的目标文件是非位置无关的,即在加载时必须进行重定位以适应不同的内存地址

  • 每个进程都需要独立的重定位: 因为库中的代码包含绝对地址的引用,加载到内存的每个进程都需要对代码段进行独立的重定位,以适应不同的内存地址。这意味着每个进程加载库时,都需要修改代码段的内容,以反映加载到的特定地址,从而创建独立的、可重定位的拷贝。

  • 无法在多个进程之间共享: 由于每个进程都需要对代码段进行独立的重定位,这导致了每个进程都会维护一个独立的、特定于该进程的库拷贝。因此,这样的库不能在多个进程之间共享,因为每个拷贝都是特定于加载它的进程的。

  • 内存占用可能更大: 每个进程都拥有自己的独立拷贝,这可能导致内存占用更大。相比之下,位置无关的库可以在多个进程之间共享,从而减少内存占用。

创建动态库: 

我们进入lib目录,使用以下命令创建静态库 libcalculator.so:

gcc -shared -o lib/libcalculator.so lib/add.o lib/multiply.o

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

动态库的使用:

编译可执行文件:​​​​​​​

​​​​​​​使用以下命令编译可执行文件:

gcc lib/main.o -o calculator_dynamic -L lib -lcalculator

运行程序:

运行生成的可执行文件:

 这时候我们会惊讶的发现动态链接生成的程序不能像静态链接一样直接运行

 这时候我们看一下系统的报错,发现提示我们找不到链接的libcalculator.so库!

可是我们使用-I-L-l这三个选项都是在编译期间已经告诉过编译器我们使用的头文件和库文件在哪里以及是谁,那么,在执行的时候是如何定位动态库文件的呢?

这时候我们使用ldd命令来查看程序的动态链接依赖关系时,我们发现c语言的动态库都能找到所在的位置,而我们自己编写的动态库却not found.

原来,系统中存在环境变量LD_LIBRARY_PATH ,用于指定动态链接器在运行时查找共享库的路径。当你运行一个程序时,系统的动态链接器会根据该变量的设置来搜索共享库的位置。

基于此我们可以有一下几种方法来使我们的程序正常执行

方法一:直接更改LD_LIBRARY_PATH

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

我们先通过pwd命令得到我们自己lib目录的绝对地址:

这时候我们再将我们得到的库的地址(记作path)添加到LD_LIBRARY_PATH环境变量当中

export LD_LIBRARY_PATH= path:$LD_LIBRARY_PATH

这里我加上自己库的path后运行程序

方法二:将目录拷贝到系统的动态库目录下

同上,我们先通过pwd命令得到我们自己lib目录的绝对地址:

 将目录中的所有共享库文件拷贝到 /usr/lib,这是系统默认的动态库目录之一。你可能需要使用 sudo 权限来执行这个命令

sudo cp path/*so /usr/lib

 不推荐将自己写的库文件拷贝到系统路径下,这样做会对系统文件造成污染,该方法仅做演示

方法三:配置/etc/ld.so.conf.d/​​​​​​​ 

我们可以通过配置/etc/ld.so.conf.d/的方式解决该问题,/etc/ld.so.conf.d/路径下存放的全部都是以.conf为后缀的配置文件,而这些配置文件当中存放的都是路径,系统会自动在/etc/ld.so.conf.d/路径下找所有配置文件里面的路径,之后就会在每个路径下查找你所需要的库。我们若是将自己库文件的路径也放到该路径下,那么当可执行程序运行时,系统就能够找到我们的库文件了。 /etc/ld.so.conf.d/ 目录下的 .conf 文件来管理库文件的路径是一种更规范的方式。这样做可以使系统更清晰地了解到库文件的位置,并且不直接修改系统默认库目录,减小了对系统的影响。

创建一个 .conf 文件:

在你的库文件所在目录创建一个以 .conf 为后缀的配置文件,比如my_library.conf,并将库文件所在目录的路径存入

将库文件所在的路径写入配置文件中: 

将 .conf 文件拷贝到 /etc/ld.so.conf.d/ 目录下:

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

这样系统会自动读取该目录下的配置文件。

使用 ldconfig更新配置:

sudo ldconfig

这个命令会重新加载配置文件,确保系统动态链接器能够找到新的库文件路径。 此时,系统应该能够正确找到你的库文件,你的可执行文件也应该能够正常运行了。

这是一种更安全和规范的方式,尤其在共享库比较多或者与其他应用程序存在依赖关系时非常有用。

        13.5 动态库与静态库怎么选? 

静态库的优势:

  • 独立性: 静态库会将库的代码嵌入到可执行文件中,使得程序在运行时不再依赖外部的库文件。这使得静态库的使用更为简单,因为用户不需要担心库文件的版本问题。
  • 性能: 静态库在编译时就已经链接到可执行文件中,因此在运行时不需要进行额外的加载和链接操作,有助于提高程序的启动速度。

动态库的优势:

  • 共享性: 动态库可以被多个程序共享使用,这有助于减小可执行文件的大小,因为多个程序可以共同使用同一个动态库,而不是每个程序都包含一份库的拷贝。
  • 更新维护: 如果库需要升级或修复 bug,只需更新动态库而无需重新编译所有依赖于它的程序。这降低了维护成本。
  • 节省内存: 动态库在内存中只需要加载一次,多个程序可以共享同一份库的实例,因此可以减少内存的占用。

选择建议:

  • 静态库: 适用于小型项目或独立的工具,可以简化部署和分发,避免依赖管理的复杂性。特别是对于一些简单的工具或嵌入式系统,静态库可能更为合适。
  • 动态库: 适用于大型项目,特别是涉及到共享代码、更新频繁或需要动态加载的场景。对于框架和库的开发者,使用动态库通常更为灵活。

最终的选择取决于项目的具体需求、开发团队的偏好以及目标平台的要求。在实际开发中,有时候也会选择混合使用,即某些库采用静态链接,而另一些采用动态链接,以充分发挥各自的优势。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【毕业设计】基于STM32的解魔方机器人

1、方案设计 1.采用舵机作为魔方机器人的驱动电机&#xff0c;从舵机的驱动原理可知&#xff1a;舵机运行的速度和控制器的主频没有关系&#xff0c;所以采用单片机和采用更高主频的嵌入式处理器相比在控制效果上没有什么差别。单片机编程过程简单&#xff0c;非常容易上手&am…

orb-slam2学习总结

目录 视觉SLAM 1、地图初始化 2、ORB_SLAM地图初始化流程 3、ORB特征提取及匹配 1、对极几何 2、对极约束 &#xff08;epipolar constraint&#xff09; 3、基础矩阵F、本质矩阵E 5、单目尺度不确定性 6、单应矩阵&#xff08;Homography Matrix&#xff09; 6.1 什么是单应矩…

【Spark精讲】RDD特性之数据本地化

首选运行位置 上图红框为RDD的特性五&#xff1a;每个RDD的每个分区都有一组首选运行位置&#xff0c;用于标识RDD的这个分区数据最好能够在哪台主机上运行。通过RDD的首选运行位置可以让RDD的某个分区的计算任务直接在指定的主机上运行&#xff0c;从而实现了移动计算而不是移…

【matlab进阶学习-6】 读取log数据data.txt文件,并做处理,导出报告/表格/图表

原始文件 原始文件格式txt&#xff0c;每一行对应一个数据&#xff0c;数据之间由逗号分割开 对应意思 时刻&#xff0c;电压&#xff0c;电流&#xff0c;功率&#xff0c;容量&#xff0c;&#xff0c;电流&#xff0c;功率&#xff0c;&#xff0c;RTC时间&#xff0c;状态…

内网服务器部署maven私服简记

前言 很多企业希望创建自己的maven私服&#xff0c;但服务器无法和外网连通&#xff0c;所以这里介绍一套完整的内网部署nexus的解决方案。实现的方式也很简单&#xff0c;将下载好的nexus安装和项目所需的依赖仓库都上传到服务i去上去&#xff0c;通过脚本的方式实现批量导入…

CSS的三大特性(层叠性、继承性、优先级---------很重要)

CSS 有三个非常重要的三个特性&#xff1a;层叠性、继承性、优先级。 层叠性 场景&#xff1a;相同选择器给设置相同的样式&#xff0c;此时一个样式就会覆盖&#xff08;层叠&#xff09;另一个冲突的样式。层叠性主要解决样式冲突 的问题 原则&#xff1a;  样式冲突&am…

autojs-练手-视频号点赞(进阶版)

注释很详细&#xff0c;直接上代码 较初阶版新增内容 1. 简单但好用的ui界面 为方便大家参考&#xff0c;ui界面的模板单独拿出来了 ui界面模板 2. opencv图像识别 3. 需加载情况特殊处理&#xff08;防卡壳&#xff09; 4. 增加自动判断是否已点赞的情况 源码部分 // 启用…

HarmonyOS4.0从零开始的开发教程14Web组件的使用

HarmonyOS&#xff08;十二&#xff09;Web组件的使用 1 概述 相信大家都遇到过这样的场景&#xff0c;有时候我们点击应用的页面&#xff0c;会跳转到一个类似浏览器加载的页面&#xff0c;加载完成后&#xff0c;才显示这个页面的具体内容&#xff0c;这个加载和显示网页的…

智能优化算法应用:基于水循环算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于水循环算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于水循环算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.水循环算法4.实验参数设定5.算法结果6.参考文…

无需公网IP联机Minecraft,我的世界服务器本地搭建教程

目录 前言 1.Mcsmanager安装 2.创建Minecraft服务器 3.本地测试联机 4. 内网穿透 4.1 安装cpolar内网穿透 4.2 创建隧道映射内网端口 5.远程联机测试 6. 配置固定远程联机端口地址 6.1 保留一个固定TCP地址 6.2 配置固定TCP地址 7. 使用固定公网地址远程联机 8.总…

Vue 中 v-model 的修饰符

lazy 修饰符&#xff1a;将 v-model 改为失去焦点后更新数据。 number 修饰符&#xff1a;将 v-model 数据转为数字类型。 trim 修饰符&#xff1a;去除 v-model 数据中的首尾空格。 语法格式&#xff1a; // lazy 修饰符 <input v-model.lazy"数据"> // nu…

靠谱的车- 华为OD统一考试(C卷)

靠谱的车- 华为OD统一考试&#xff08;C卷&#xff09; OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 程序员小明打了一辆出租车去上班。出于职业敏感&#xff0c;他注意到这辆出租车的计费表有点问题&#xf…

【JNA与C++基本使用示例】

JNA中java与C使用注意事项和代码示例 JNA关系映射表使用案列注意代码示例C代码java代码 JNA关系映射表 使用案列 注意 JNA只支持C方式的dll使用C的char* 作为返回值时&#xff0c;需要返回的变量为malloc分配的地址C的strlen函数只获得除/0以外的字符串长度 代码示例 C代码…

基于PaddleNLP的深度学习对文本自动添加标点符号(一)

前言 目前以深度学习对文本自动添加标点符号研究很少&#xff0c;已知的开源项目并不多&#xff0c;详细的介绍就更少了&#xff0c;但对文本自动添加标点符号又在古文识别语音识别上有重大应用。 基于此&#xff0c;本文开始讲解基于PaddleNLP的深度学习对文本自动添加标点符号…

鸿蒙开发之状态管理@Prop和@Link

一、用法 在父子组件需要进行数据同步的时候&#xff0c;可以通过Prop和Link装饰器来做到。在父组件中用State装饰&#xff0c;在自组件中用Prop或Link装饰。 结论&#xff1a;Prop用于子组件只监听父组件的数据改变而改变&#xff0c;自己不对数据改变 Link用于子组件与父组…

Proxmox VE 安装 OpenWrt 配置旁路由教程

话不多说&#xff0c;本篇文章将记录如何在 Proxmox VE 环境通过虚拟机安装 OpenWrt 配置旁路由的过程&#xff0c;仅做参考。 PVE 创建虚拟机 名称随意&#xff0c;GuestOS 选择 Linux&#xff0c;不使用任何 iso 镜像。&#xff08;记住你的 VMID&#xff09; 清空将要创建…

机器学习---Adaboost算法

1. Adaboost算法介绍 Adaboost是一种迭代算法&#xff0c;其核心思想是针对同一个训练集训练不同的分类器&#xff08;弱分类器&#xff09;&#xff0c;然 后把这些弱分类器集合起来&#xff0c;构成一个更强的最终分类器&#xff08;强分类器&#xff09;。Adaboost算法本身…

Qt 线程

&#x1f4a1; 进度条显示拷贝进度&#xff08;verson 1&#xff09; 窗口上放置一个按钮和一个进度条部件&#xff0c;点击按钮&#xff0c;进行拷贝操作 —— 打开对话框选择源文件&#xff0c;然后再打开一个对话框 选择 目标文件存放位置和名称。拷贝过程中进度条显示当前…

十三、YARN资源分配调用

1、为什么要先学习YARN组件&#xff1f; 在Hadoop文件系统中&#xff0c;YARN作为Hadoop系统的第三大组件&#xff0c;其中&#xff0c;第二大组件MapReduce组件是基于YARN运行的&#xff0c;即没有YARN无法运行MapReduce程序&#xff0c;所以需要同时学习YARN。 2、YARN &…

Day58力扣打卡

打卡记录 下一个更大元素 IV&#xff08;单调栈 x2&#xff09; 链接 class Solution:def secondGreaterElement(self, nums: List[int]) -> List[int]:ans [-1] * len(nums)s []t []for i, x in enumerate(nums):while t and nums[t[-1]] < x:ans[t.pop()] x # t…