深入理解C语言中的静态库与动态库 —— 原理与实践
引言
在 C 语言编程中,库是预编译的代码集合,用于实现特定功能,以供其他程序使用。库可以分为静态库和动态库两种主要类型。静态库在编译阶段被链接到目标程序中,而动态库则是在运行时被加载。本文旨在深入探讨这两种库的工作原理、优缺点以及它们在实际项目中的应用。
静态库与动态库的基础知识
1.1 静态库
定义及用途:
静态库是一组预先编译好的目标文件的集合,这些文件包含了一个或多个源文件的函数和数据。当一个程序被链接时,静态库中的代码会被复制到最终的可执行文件中,使得每个使用该库的程序都包含了库的副本。
创建和使用:
假设我们有一个简单的库libfoo.a
,其中包含一个函数foo()
。
// foo.c
void foo() {printf("Hello from foo!\n");
}
可以使用以下命令来创建静态库:
gcc -c foo.c # 编译源文件生成目标文件 foo.o
ar rcs libfoo.a foo.o # 将目标文件打包成静态库
ranlib libfoo.a # 更新静态库的索引信息
然后,在主程序中使用它:
#include <stdio.h>
#include "foo.h"int main() {foo();return 0;
}
链接静态库到主程序:
gcc main.c -L. -lfoo -o main
链接过程:
- 当链接器遇到
-lfoo
选项时,它会在指定的目录(这里是.
表示当前目录)查找名为libfoo.a
的静态库。 - 链接器读取库中的符号表,寻找未定义的符号(如
foo
)。 - 如果找到匹配的符号,则链接器将相应的代码和数据复制到最终的可执行文件中。
优势:
- 可移植性:静态库与平台无关,可以在任何支持相同编译器的平台上使用。
- 独立性:静态库被链接到可执行文件中,这意味着可执行文件不需要外部库就能运行。
- 部署简单:静态链接的程序只需要一个可执行文件即可运行,无需额外安装库。
劣势:
- 可执行文件体积大:每个使用静态库的程序都会包含一份完整的库代码,导致可执行文件体积增大。
- 内存占用多:如果多个程序同时运行并且都使用相同的静态库,那么这些程序在内存中会各自保留一份库代码的副本,导致内存使用量增加。
- 难以更新:一旦静态库被链接到程序中,就很难更新库中的代码而不重新编译和链接整个程序。
1.2 动态库
定义及用途:
动态库(或共享库)在程序运行时才被加载。它们通常存储在一个单独的位置,并且可以在多个程序之间共享。这有助于减少磁盘空间的占用和提高内存使用的效率。
创建和使用:
假设我们有一个简单的库libbar.so
,其中包含一个函数bar()
。
// bar.c
void bar() {printf("Hello from bar!\n");
}
可以使用以下命令来创建动态库:
gcc -shared -fPIC -o libbar.so bar.c
然后,在主程序中使用它:
#include <stdio.h>
#include <dlfcn.h>int main() {void (*bar)();void *handle = dlopen("./libbar.so", RTLD_LAZY);if (!handle) {fprintf(stderr, "%s\n", dlerror());return 1;}bar = (void (*)())dlsym(handle, "bar");if ((void *)bar == NULL) {fprintf(stderr, "%s\n", dlerror());dlclose(handle);return 1;}bar();dlclose(handle);return 0;
}
加载机制:
- 在程序启动时,动态链接器会根据配置的搜索路径(如
LD_LIBRARY_PATH
环境变量)查找所需的动态库。 - 动态链接器将找到的动态库加载到内存中,并解析其中的符号引用。
- 符号解析完成后,程序可以调用动态库中的函数。
优势:
- 内存节约:动态库可以被多个程序共享,从而减少内存使用。
- 易于更新:动态库可以在不重新编译和链接程序的情况下更新。
- 可扩展性:动态库可以方便地添加新功能而不会影响已有的程序。
劣势:
- 加载时间长:由于动态库需要在运行时加载,可能会导致程序启动时间稍长。
- 部署复杂:需要确保动态库存在于系统的适当位置,并且所有依赖项都正确安装。
- 版本兼容性问题:不同版本的动态库可能导致程序行为变化。
静态库与动态库的区别
- 存储方式:静态库中的代码在编译期间被直接嵌入到最终的可执行文件中;而动态库则作为独立的文件存在,由动态链接器在运行时加载。
- 链接时刻:静态库在编译阶段被链接,而动态库则在运行时被加载。
- 文件大小与加载速度:由于静态库中的代码被重复复制到每一个使用它的程序中,因此使用静态库的程序往往体积更大;动态库因为可以被多个程序共享,所以文件大小较小,但加载速度可能较慢。
- 内存占用与程序启动时间:使用动态库的程序在启动时需要额外的时间加载共享库,但在运行过程中,共享库仅被加载一次,节省了内存资源。
- 维护与更新:动态库更容易更新,因为它只需替换文件即可生效,而静态库一旦链接到可执行文件后就很难更新。
创建和使用示例
2.1 静态库示例
假设我们已经创建了libfoo.a
,现在我们将展示如何将它链接到一个简单的程序中:
gcc main.c -L. -lfoo -o main
链接过程详解:
gcc
调用链接器时,它会检查所有的.o
文件和静态库。- 对于每一个未定义的符号(如
foo
),链接器会在静态库中查找对应的定义。 - 当找到匹配的定义时,链接器会将相应的代码段和数据段复制到最终的可执行文件中。
示例输出:
$ ./main
Hello from foo!
2.2 动态库示例
假设我们已经创建了libbar.so
,现在我们将展示如何在程序中使用它:
gcc main.c -ldl -o main
加载机制详解:
- 当程序启动时,动态链接器会尝试加载程序依赖的所有动态库。
- 动态链接器会解析动态库中的符号表,并将符号绑定到正确的内存位置。
- 程序可以通过调用
dlopen
、dlsym
和dlclose
等函数来手动加载和卸载动态库。
示例输出:
$ ./main
Hello from bar!
性能考虑
内存使用效率:动态库可以被多个程序共享,因此减少了内存的使用。静态库中的代码在每个程序中都有一个副本,增加了内存消耗。
加载时间:静态库中的代码在编译时就被整合进程序中,因此不需要额外的加载时间。而动态库需要在程序启动时加载,这可能会稍微增加启动时间。
动态链接的优化:现代操作系统提供了延迟加载和按需加载的技术,可以显著减少动态库加载带来的开销。例如,在Linux上,动态链接器可以延迟加载那些在程序执行过程中并不立即需要的动态库。
实际项目中的选择
在实际项目中,选择使用静态库还是动态库取决于多个因素:
- 可维护性:动态库易于更新和维护,不需要重新编译和发布整个应用程序。
- 部署便利性:静态库简化了部署过程,因为所有依赖都已经被整合进程序本身。
- 跨平台支持:某些平台可能不支持动态库或者有不同的动态库格式,例如Windows下的DLL与Linux下的SO。
- 安全性:动态库可以利用ASLR等安全特性来提高程序的安全性。
示例场景:
- 游戏开发:在游戏开发中,为了获得最佳性能,通常会选择静态库来减少加载时间和内存使用。
- 企业级软件:对于需要频繁更新的企业级软件来说,使用动态库可以降低维护成本并提高更新效率。
深度解析
3.1 编译与链接过程
编译阶段:
- 源代码被编译器转换为目标代码,这个过程会生成
.o
文件,这些文件包含了编译后的指令和符号表。
链接阶段:
- 链接器负责将多个
.o
文件和库文件合并成一个可执行文件。 - 链接器解决不同模块之间的地址偏移问题,确保所有符号正确引用。
重定位:
- 链接器在生成可执行文件时需要进行重定位,以确保所有符号的地址正确无误。
- 重定位信息存储在
.o
文件和库文件中,链接器根据这些信息调整符号地址。
3.2 动态链接器的工作原理
动态库搜索路径:
- 系统会根据
/etc/ld.so.conf
文件和LD_LIBRARY_PATH
环境变量来确定动态库的搜索路径。 - 动态链接器会扫描这些路径,寻找必要的共享库。
延迟加载:
- 现代动态链接器支持延迟加载技术,即只有当程序试图访问某个动态库中的符号时,才会加载相应的动态库。
- 这种技术可以显著减少程序启动时间。
符号解析:
- 动态链接器负责解析程序依赖的符号引用,并将它们绑定到正确的内存位置。
- 解析过程包括全局和局部符号的处理。
案例研究:
- 动态库更新:假设一个程序使用了旧版本的动态库,而新的版本修复了一些重要的安全漏洞。只需将新版本的动态库放置在正确的位置,程序就可以自动使用新版本的库,而无需重新编译或重新链接程序。
3.3 性能分析工具
工具推荐:
- gprof:用于收集程序执行时的性能统计数据。
- valgrind:可以检测内存泄漏和错误使用。
- perf:用于系统级别的性能监控。
性能测试:
- 可以通过编写测试脚本来比较使用静态库和动态库的程序在内存使用和加载时间方面的表现。
- 使用上述工具来分析程序的性能,并记录结果。
示例实验设计:
-
实验步骤:
- 构建使用静态库的程序。
- 构建使用相同功能的动态库版本的程序。
- 使用
gprof
或perf
对两个版本的程序进行基准测试。 - 分析结果,对比内存使用和加载时间。
-
预期结果:动态库版本的程序可能会显示出较低的内存使用率和较长的加载时间,而静态库版本的程序可能会有较高的内存使用率和较短的加载时间。
结论
静态库和动态库各有优势和劣势。静态库适用于需要严格控制资源使用的情况,而动态库更适合于需要频繁更新的应用。开发者应该根据项目的具体需求来做出选择。
相关文章:
深入理解C语言中的静态库与动态库 —— 原理与实践
引言 在 C 语言编程中,库是预编译的代码集合,用于实现特定功能,以供其他程序使用。库可以分为静态库和动态库两种主要类型。静态库在编译阶段被链接到目标程序中,而动态库则是在运行时被加载。本文旨在深入探讨这两种库的工作原理…...
本地缓存库分析(一):golang-lru
文章目录 本地缓存概览golang-lru标准lrulru的操作PutGet 2q:冷热分离lruPutGet expirable_lru:支持过期时间的lruPutGet过期 总结 本地缓存概览 在业务中,一般会将极高频访问的数据缓存到本地。以减少网络IO的开销,下游服务的压…...
qt配置https请求
qt应用版本 windows 32位 先说下心理路程,你能遇到的我都遇到了,你能想到的我都想到了,怎么解决看这一篇就够了,从上午12点到晚上12点几乎没离开电脑(除了吃饭),对于openssl这种用的时候无感&am…...
C语言进阶——文件操作
一、文件的基本知识 1.1什么是文件 在程序设计中,一般谈的文件有两种:程序文件、数据文件。 程序文件:包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执…...
MYSQL-查看用户权限语法(二十一)
13.7.5.21 SHOW GRANTS 语句 SHOW GRANTS [FOR user]此语句以GRANT语句的形式显示分配给MySQL用户帐户的权限,必须执行GRANT语句才能复制权限分配。 注意 要显示MySQL帐户的非特权信息,请使用SHOW CREATE USER语句。 参见第 13.7.5.12 节“ SHOW CREA…...
在MySQL中存储IP地址的最佳实践
文章目录 一、IP地址的格式二、存储IP地址的数据类型选择1. VARCHAR优点缺点 2. INT 或 BIGINT优点缺点示例 3. VARBINARY优点缺点示例 三、最佳实践建议1. 选择合适的数据类型2. 索引优化3. 数据验证4. 安全性考虑 四、Java支持五、结论 在现代网络应用中,IP地址是…...
Vite打包配置
Vite打包配置 1.项目启动自动打开网页 {"scripts": {"dev": "vite --open"} }2.base配置打包公共路径 配置base选项的作用主要是指定项目在开发或生产环境中的公共基础路径。这个配置项对于确保资源能够正确加载尤为关键,尤其是在…...
node集成redis (教学)
文章目录 前言一、安装redis二、可视化界面测试连接1.vscode安装插件 三、node代码编写1.先安装两个库(redis和ioredis)2.测试连接 (前提是你的redis服务器要启动起来) 总结 前言 在Node.js中集成ioredis是一个常见的做法&#x…...
江协科技STM32学习- P22 实验-ADC单通道/ADC多通道
🚀write in front🚀 🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝…...
RL学习笔记-马尔可夫过程
参考资料:蘑菇书、周博磊老师课程 在强化学习中,智能体与环境交互是通过马尔可夫决策过程来表示的,因此马尔可夫决策过程是强化学习的基本框架。 马尔可夫性质 指一个随机过程在给定现在状态及所有过去状态情况下,其未来状态的条件…...
LeetCode Hot 100:动态规划
LeetCode Hot 100:动态规划 70. 爬楼梯 class Solution { public:int climbStairs(int n) {if (n 0)return 0;vector<int> dp(n 1);// 初始化dp[0] 1;// 状态转移for (int i 1; i < n; i) {dp[i] dp[i - 1];if (i > 2)dp[i] dp[i - 2];}return …...
使用Python制作雪景图片教程
如果你想用Python写一个程序来输出有关“深夜雪”的诗意文本或描述,可以通过简单的字符串输出来实现。以下是一个示例代码,展示如何用Python来描绘深夜雪的场景。 # 定义深夜雪的描述 description """ 夜幕降临,天空洒下银色…...
S-Function
目录 S-Function介绍 生成S-Function的三种常用手段 使用手写S-函数合并定制代码 使用S-Function Builder块合并定制代码 使用代码继承工具合并定制代码 S-Function介绍 我们可以使用S-Function扩展Simulink对仿真和代码生成的支持。例如,可以使用它们…...
如何具备阅读JAVA JDK虚拟机源码能力
源码位置https://github.com/openjdk/jdk 核心实现源码[部分截图] /* * Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistr…...
Python | Leetcode Python题解之第514题自由之路
题目: 题解: Test "godding" target "d"i 0left i lc 0 right i rc 0while Test[left] ! target:left - 1lc 1if left -1:left len(Test) - 1while Test[right] ! target:right 1rc 1if right len(Test):right 0prin…...
Docker 镜像下载问题及解决办法
Docker 镜像下载问题及解决办法 我在杂乱的、破旧的村庄寂寞地走过漫长的雨季,将我年少的眼光从晦暗的日子里打捞出来的是一棵棵开花的树,它们以一串串卓然不俗的花擦明了我的眼睛,也洗净了我的灵魂。 引言 在使用 Docker 时,用户…...
2分钟搞定 HarmonyOs Next创建模拟器
官方文档参考链接: 创建模拟器-管理模拟器-使用模拟器运行应用/服务-应用/服务运行-DevEco Studio - 华为HarmonyOS开发者https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-emulator-create-V5 1. 首先打开Device Manager 2. 进入这个界面后…...
方形件排样优化与订单组批问题探析
方形件排样优化与订单组批问题是计算复杂度很高的组合优化问题,在工业工程中有很广泛的应用背景。为实现个性化定制生产模式,企业会选择订单组批的方式,继而通过排样优化实现批量切割,加工完成后再按照不同客户需求进行分拣&#…...
vue3组件通信--自定义事件
自定义事件是典型的子传父的方法。 为什么叫自定义事件呢?是因为我们用sendToy"getToy"这种格式写,很显然,在DOM中,没有叫sendToy的事件。 父组件FatherComponent.vue: <script setup> import ChildComponent fr…...
ubuntu 安装k3s
配置hostname的方法为 hostnamectl set-hostname k3sserver hostnamectlsudo apt-get update && sudo apt-get upgrade -y sudo apt-get install -y curl#手动下载v1.31.1k3s1 https://github.com/k3s-io/k3s/releases/tag/v1.31.1%2Bk3s1 #将k3s-airgap-images-amd64…...
SQL CHECK 约束:确保数据完整性的关键
SQL CHECK 约束:确保数据完整性的关键 在数据库管理中,确保数据的完整性和准确性是至关重要的。SQL(Structured Query Language)提供了多种约束条件来帮助实现这一目标,其中之一就是 CHECK 约束。本文将深入探讨 SQL CHECK 约束的概念、用法和优势,并展示如何在不同的数…...
C++ | Leetcode C++题解之第502题IPO
题目: 题解: typedef pair<int,int> pii;class Solution { public:int findMaximizedCapital(int k, int w, vector<int>& profits, vector<int>& capital) {int n profits.size();int curr 0;priority_queue<int, vect…...
《虚拟现实的边界:探索虚拟世界的未来可能》
内容概要 在虚拟现实(VR)技术的浪潮中,我们见证了其从实验室的奇想逐渐走向日常生活的非凡旅程。技术发展的背后是不断突破的创新,早期的设备虽然笨重,但如今却趋向精致、轻巧,用户体验显著提升。想象一下…...
Rust教程
2024 Rust现代实用教程:1.1Rust简介与安装更新––2024 Rust现代实用教程:1.2编译器与包管理工具以及开发环境–––––––––––...
测试代理IP的有效性和可用性
使用代理IP的有效性和可用性直接关系到用户的工作效率,尤其是在进行数据抓取、网络爬虫和保护个人隐私等场景中。 一、测试代理IP的必要性 代理IP的可用性测试是确保代理服务正常运行的重要步骤。测试代理IP的必要性主要体现在以下几个方面: 提升工作…...
散列表:为什么经常把散列表和链表放在一起使用?
散列表:为什么经常把散列表和链表放在一起使用? 在计算机科学中,散列表(哈希表)和链表是两种常见的数据结构。你可能会好奇,为什么它们经常被放在一起使用呢?让我们一起来深入探讨这个问题。 一、散列表的特点 散列表是一种根据关键码值(Key value)而直接进行访问的…...
计算机网络:网络层 —— IPv4 地址与 MAC 地址 | ARP 协议
文章目录 IPv4地址与MAC地址的封装位置IPv4地址与MAC地址的关系地址解析协议ARP工作原理ARP高速缓存表 IPv4地址与MAC地址的封装位置 在数据传输过程中,每一层都会添加自己的头部信息,最终形成完整的数据包。具体来说: 应用层生成的应用程序…...
PMP--一、二、三模、冲刺、必刷--分类--10.沟通管理--技巧--文化意识
文章目录 技巧一模10.沟通管理--1.规划沟通管理--文化意识--军事背景和非军事背景人员有文化差异文化意识:题干关键词 “两拨人的背景不同、文化差异、风格差异”。5、 [单选] 项目团队由前军事和非军事小组成员组成。没有军事背景的团队成员认为前军事团队成员在他…...
FileReader和FileWriter
FileReader 使用read()方法读取单个字符,下面是如何修改使程序性能更好的过程。 第一种:处理异常方式为throws Testpublic void test() throws IOException {//读取hello.txt,并显示内容// 创建文件对象File file new File("hello.txt…...
【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第六篇-阶段总结篇】
因为马上就要进入下一个阶段,制作动态编辑体积纹理的模块。 但在这之前,要在这一章做最后一些整理。 首先,我们完成没完成的部分。其次,最后整理一下图表。最后,本文附上正在用的贴图 完善Shader 还记得我们之前注…...
西安好的皮肤管理做团购网站/seo优化实训总结
2019独角兽企业重金招聘Python工程师标准>>> 今天在操作mysql数据库时,犯了一个大错误,执行了一个更新语句,但时条件没写全,结果在执行了几秒之后才发现这个问题,将一些不该改变的记录值改变了。1天时间全部…...
罗源网站建设/山东建站管理系统
近期最终把effectvie C细致的阅读了一边,非常惊叹C的威力与魅力。近期会把近期的读书心得与读书笔记记于此。必备查找使用,假设总结有什么不当之处,欢迎批评指正: 如今仅仅列出框架。近期会尽快填充完整&…...
云酒店网站建设/网站管理
使用VBA对指定的单元格赋值并填充颜色 代码区域 Sub row应用()For Each rw In Rows("1:13")If rw.Row Mod 2 0 Thenrw.Interior.ColorIndex 3rw.Value 99End IfNext End Sub 效果如下图: 转载于:https://www.cnblogs.com/OliverQin/p/6201371.html...
石家庄市建设局质监站网站/网络站点推广的方法有哪些
前言 在SpringBoot中使用自定义注解、aop切面打印web请求日志。主要是想把controller的每个request请求日志收集起来,调用接口、执行时间、返回值这几个重要的信息存储到数据库里,然后可以使用火焰图统计接口调用时长,平均响应时长&am…...
教育网站制作/百度seo排名优化助手
点到点拓补中的OSPF运行 1)点对点网络的介绍:在点对点网络上,路由器通过使用组播地址224.0.0.5发送Hello数据包,检查它的邻居。点到点网络不需要选举DR和BDR,在2台路由器能够直接通信时,他们就形成了相邻关…...
网站建设与制作实训报告/全球搜索
本文的主要讲述的是在PHP中调用MySQL数据库的基本操作代码以及解释,具有一定的参考价值,有需要的朋友一定要好好看看!PHP-MySQL基本操作<?php // 1.防止页面中文乱码header("content-type:text/html;charsetutf-8");// 链接数据…...