【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_H

int 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,只需更新动态库而无需重新编译所有依赖于它的程序。这降低了维护成本。
  • 节省内存: 动态库在内存中只需要加载一次,多个程序可以共享同一份库的实例,因此可以减少内存的占用。

选择建议:

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue 中 v-model 的修饰符

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

ArcGIS pro与SuperMap根据属性自动填充颜色步骤

GIS项目经常会接触到控规CAD数据&#xff0c;想要把数据转换成GIS图层并发布&#xff0c;需要进行专题配图。研究了一下ArcGIS pro和SuperMap iDesktop的配图&#xff0c;整理一下用到的一些技术思路。 1、Excel表格根据RGB值添加单元格填充颜色 要实现如上效果图&#xff0c;…

AI创新之美:AIGC探讨2024年春晚吉祥物龙辰辰的AI绘画之独特观点

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《粉丝福利》 《linux深造日志》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引言一、龙辰辰事件概述二、为什么龙辰辰会被质疑AI创作&#xff1f;1.1 AI 作画的特点2.2 关于建行的合作宣传图…

如何利用宝塔面板和docker快速部署网站

当你有了一台服务器&#xff0c;就会折腾往这台服务器上部署各种好玩的网站。市面上有许多开源的网站项目&#xff0c;通过docker技术可以快速部署并使用&#xff0c;本文将以部署filebrowser举例介绍网站部署的基本流程。 1. 安装宝塔面板 宝塔面板是一款开源的网站运维工具…

博客内容和多种搜索友好格式在 2024 年的作用

内容和SEO之间一直存在着密切的联系。 今天&#xff0c;我想分享一些关于博客、内容和搜索友好格式的想法和技巧。此外&#xff0c;我将分享有关生成式人工智能如何为这种关系添加新元素的见解。 谷歌最近关于有用和有价值的内容的更新重新引发了关于如何找到内容以及内容排名…

汽车销售技巧培训应该学习哪些内容

汽车销售技巧培训应该学习哪些内容 随着汽车市场的竞争日益激烈&#xff0c;汽车销售技巧培训对于提高销售人员的销售能力和服务水平至关重要。本文将介绍汽车销售技巧培训应该学习哪些内容&#xff0c;并结合案例进行分析。 一、产品知识 作为销售人员&#xff0c;了解所销售…

用户管理第2节课 -- idea 2023.2 创建表

一、懂得 1.1编码格式是防止乱码的&#xff0c;utf-8是完全够的&#xff0c;那几个基本没差别 网址&#xff1a; 【IDEA——连接MySQL数据库&#xff0c;创建库和表】_idea中数据库-CSDN博客 这些是MySQL数据库中的一些术语&#xff0c;可以简单解释如下&#xff1a; 1、col…

【日积月累】Spring中的AOP与IOC相关问题详解

Spring中的AOP与IOC 1.前言2.Spring AOP&#xff08;面向切面编程&#xff09;2.1 AOP的实现过程2.2 AOP代理模式的类型2.2.1JDK的动态代理2.2.2CGLIB的动态代理 2.3AOP应用常见场景2.3.1日志记录 2.4对AOP的理解 3.Spring IOC&#xff08;Inversion of Control&#xff0c;控…

低代码平台解密:探秘MQTT协议的应用之道

本文由葡萄城技术团队发布。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 前言 低代码平台作为当今快速发展的技术之一&#xff0c;为开发人员提供了更高效、更简便的工具和方法&#xff0c;以…

深度解读 Cascades 查询优化器

数据库中查询优化器是数据库的核心组件&#xff0c;其决定着 SQL 查询的性能。Cascades 优化器是 Goetz 在 volcano optimizer generator 的基础上优化之后诞生的一个搜索框架。 本期技术贴将带大家了解 Cascades 查询优化器。首先介绍 SQL 查询优化器&#xff0c;接着分析查询…

【玩转TableAgent数据智能分析】TableAgent全功能详解及多领域数据分析实践(下)数据分析过程及总结展望

6 TableAgent的数据分析过程解析 TableAgent的整个分析过程包括以下步骤&#xff0c;形成一个有机结构&#xff0c;让我们理清其工作原理。 6.1 Data Graph阶段 TableAgent首先绘制数据图&#xff0c;以解决问题。这个图形表示了问题的分解和细化&#xff0c;将大问题分解成…

git强制回滚,远程强制更新,git pull强制更新

注意&#xff1a;这里是强制回滚&#xff0c;回滚后&#xff0c;之后历史的就没有了&#xff0c;慎用。 本地强制回滚 强制回滚到上一个版本 git reset --hard HEAD^强制回滚上上个版本 git reset --hard HEAD^^git log查看版本 git log --prettyonelinegit log --prettyf…

【网络安全】HTTP Slowloris攻击原理解析

文章目录 Slowloris攻击的概念Slowloris攻击原理Slowloris攻击的步骤其他的DDoS攻击类型UDP FloodICMP (Ping) FloodSYN FloodPing of DeathNTP AmplificationHTTP FloodZero-day DDoS 攻击 推荐阅读 Slowloris攻击的概念 Slowloris是在2009年由著名Web安全专家RSnake提出的一…

指定安装nginx版本链接

Index of /packages/centos/7/x86_64/RPMS/ (nginx.org) 找到想要下载的对应版本直接下载 rpm -ivh http://nginx.org/packages/centos/7/x86_64/RPMS/nginx-1.24.0-1.el7.ngx.x86_64.rpm 查看nginx信息 rpm -qa nginx rpm -qi nginx 命令rpm -ivh是Linux系统中的一种用于…

折腾了一下Atmega64A(开发环境搭建+程序下载)

半路接了一个项目&#xff0c;使用的mcu是atmega64a&#xff0c;在我印象中这种古老芯片都要淘汰了&#xff0c;没想到还有人在使用。 程序是用的ICCV7 for AVR开发的&#xff0c;在网上找到这个IDE&#xff0c;win10下安装还算顺利&#xff0c;这个软件的最新版本是7.22&#…

安科瑞出席宁波市建筑电气2023年年会-安科瑞 蒋静

12月1日&#xff0c;宁波市建筑电气2023年年会在宁波市海曙天港禧悦酒店成功举办。作为推动宁波市建筑电气行业技术发展的专业交流会&#xff0c;吸引了建筑电气行业领导、专家、设计师、厂家等300多名代表参会。期间&#xff0c;安科瑞电气股份有限公司携智能楼宇、智慧校园、…

Etcd实战(二)-k8s集群中Etcd数据存储

1 介绍 k8s中所有对象的manifest都需要保存到某个地方&#xff0c;这样他们的manifest在api server重启和失败的时候才不会丢失&#xff0c;因此引入了etcd。在k8s中只有api server和etcd直接交互&#xff0c;其它组件都通过api server间接和etcd交互&#xff0c;这样做的好处…

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

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

PostgreSQL本地数据库密码忘记的解决办法

一&#xff1a;找到pgsql的安装路径下的data文件夹里的pg_hba.conf 文件 二&#xff1a;将该文件夹里的如下两个md5改成trust &#xff08;部分教程上只让改第一个&#xff0c;在我这只改第一个并不能跳过密码&#xff0c;要两个一块改才行&#xff09; 三&#xff1a;运行c…

Java网络编程,使用UDP实现TCP(三), 基本实现四次挥手

简介 四次挥手示意图 在四次挥手过程中&#xff0c;第一次挥手中的Seq为本次挥手的ISN&#xff0c; ACK为 上一次挥手的 Seq1&#xff0c;即最后一次数据传输的Seq1。挥手信息由客户端首先发起。 实现步骤&#xff1a; 下面是TCP四次挥手的步骤&#xff1a; 第一次挥手&…