当前位置: 首页 > news >正文

函数库 动静态库

函数库 && 动静态库

第一部分:C语言函数库概述

1. 什么是函数库?

你可以把函数库想象成一个代码仓库工具箱。它是一系列预先编写好的、可重用的函数、变量和编译后 代码的集合。

  • 为什么需要它?:为了提高效率和解耦。你不需要每次都重新发明轮子(比如自己写一个打印函数printf或数学函数sqrt),直接使用库里的现成代码即可。这大大加快了开发速度,并保证了代码的可靠性和一致性。
2. C语言函数库的组成

C语言的函数库主要分为两个部分:

  1. C标准库 (The C Standard Library)
    • 定义:由C语言标准(如C89、C99、C11等)明确定义的一套核心函数库。任何支持C语言的编译器必须提供这个库。

    • 内容:包含了一系列头文件(.h),声明了常用的函数、类型和宏。实现这些函数的代码已经被编译好,并打包在编译器的运行时库中。

    • 常见头文件示例

      • stdio.h:标准输入输出(printf, scanf, FILE操作)
      • stdlib.h:标准库函数(内存分配malloc, 随机数rand, 系统调用system
      • string.h:字符串处理函数(strcpy, strlen, strcmp
      • math.h:数学函数(sin, cos, sqrt, pow
      • time.h:时间和日期函数

  1. 第三方库 (Third-party Libraries)
  • 定义:由其他组织或个人开发的、提供特定功能的库。它们不是C语言标准的一部分,你需要单独下载和安装。
  • 示例
    • OpenSSL:加密和安全通信。
    • libcurl:网络数据传输(如HTTP请求)。
    • SQLite:轻量级数据库引擎。
    • SDL:多媒体开发(游戏、音频、图形)。
3. 头文件 vs. 库文件

这是一个非常关键的区别,常常让初学者困惑:

特性头文件 (Header Files, .h)库文件 (Library Files, .a / .so / .dll / .lib)
内容声明 (Declarations)定义 / 实现 (Definitions / Implementations)
形式文本文件,人类可读二进制文件,机器可读
作用告诉编译器“有什么”告诉链接器“怎么做”
类比菜谱的目录(告诉你有什么菜,需要什么原料)已经做好的成品菜或半成品(可以直接使用)

工作流程

  1. 编译期:你#include <stdio.h>。编译器看到printf声明(函数名、返回值、参数类型),相信这个函数是存在的,顺利通过编译,生成目标文件(.o)。
  2. 链接期:链接器去库文件(如libc.so)中找到printf函数编译后的二进制代码(即定义),并将其合并到最终的可执行程序中。

第二部分:静态库 (Static Libraries)

1. 概念与特点
  • 文件名:在Unix-like系统(Linux, macOS)上通常以.a结尾(Archive)。Windows上为.lib
  • 工作原理:在链接期,链接器会将你的程序中所用到的库中的函数代码,完整地复制到最终生成的可执行文件中。
  • 优点
    1. 独立性:可执行文件自成一体,不依赖运行时环境是否存在特定的库文件。发布程序时非常方便,不容易因库版本问题出错。
    2. 性能:理论上,函数调用没有动态链接的额外开销(但开销极小,可忽略)。
  • 缺点
    1. 空间浪费:如果多个程序都使用了同一个静态库,那么每个程序的内部都会有一份相同的库代码副本,浪费磁盘和内存空间。
    2. 难以更新:如果库发现了bug或需要升级,你必须重新编译整个程序,并重新分发整个巨大的可执行文件。用户无法通过仅更新库来修复问题。
2. 创建与使用(Linux/macOS示例)

//这个我还没学到,我学了再写。大家先了解一下动静态库的概念先,我们先有一个大体的印象,了解一下动静态库的工作机制。创建和使用我学了会写的。


第三部分:动态库 (Dynamic Libraries / Shared Libraries)

1. 概念与特点
  • 文件名:在Unix-like系统(Linux)上通常以.so结尾(Shared Object)。Windows上为.dll(Dynamic-Link Library),配套的引入库为.lib
  • 工作原理
    1. 编译链接期:链接器只会在可执行文件中记录一些信息,表明这个程序需要用到哪个动态库(符号引用),而不会复制库的代码。
    2. 运行期:当程序被加载到内存准备执行时,操作系统的动态链接器会负责查找所需的动态库,并将其加载到内存中。程序中的所有实例共享这一份库代码。
  • 优点
    1. 节省资源:磁盘上只有一个库文件,内存中只需加载一份,所有程序共享。大大节省了空间。
    2. 易于更新:更新库非常方便。只需用新版本的.so文件替换旧版本即可(注意接口兼容性)。所有依赖它的程序在下次运行时会自动使用新库,无需重新编译
    3. 插件系统:非常适合实现插件架构,主程序可以在运行时动态加载和卸载功能模块。
  • 缺点
    1. 依赖管理:发布程序时,必须确保目标机器上安装了所有所需版本的动态库,否则程序将无法启动(经典的“找不到xxx.dll”错误)。
    2. 轻微性能损耗:第一次调用函数时有额外的链接开销,以及函数调用需要通过一个额外的间接跳转(PLT/GOT表),但对现代CPU来说损耗极小。

在这里插入图片描述

为什么它就是 C 标准库?
  1. 名称 (libc.so.6)
    • lib:是库文件的标准前缀。
    • c:代表 C 语言标准库。
    • so:是 Shared Object 的缩写,表明这是一个动态库(共享库)
    • 6:代表主版本号,指的是 Glibc 的版本 2.x 系列对应的 SONAME(共享库名)。这个编号和 Glibc 的版本号有对应关系。
  2. 来源 (ldd 命令)
    • 运行的 ldd test 命令列出了名为 test 的可执行程序所依赖的所有动态库
    • 输出显示 test 程序依赖于 libc.so.6,并且系统在 /lib64/ 目录下找到了它。这证实了程序调用了 C 标准库中的函数(比如 printf, malloc 等)。
  3. 符号链接 (->)
    • 通过 ll (等同于 ls -l) 命令查看,发现 /lib64/libc.so.6 并不是一个真正的文件,而是一个符号链接(相当于 Windows 的快捷方式)。
    • 它指向一个具体的、带版本号的文件:libc-2.17.so
    • libc-2.17.so 才是真正的、包含所有编译好二进制代码的动态库文件。这里的 2.17 就是这个系统上安装的 Glibc 的具体版本号。
2. 创建与使用(Linux/macOS示例)

//同上,我还没学。


第四部分:动静态库的核心区别与总结

特性静态库 (.a)动态库 (.so / .dll)
链接时机编译链接期编译链接期(记录) + 运行期(加载)
代码存在形式复制到可执行文件内部独立文件,与可执行文件分离
文件大小可执行文件可执行文件
内存占用多份拷贝,占用多一份拷贝,共享占用少
部署难度简单,单个文件即可运行复杂,需确保目标系统有正确版本的库
更新维护困难,需重新编译整个程序简单,替换库文件即可,无需重新编译程序
兼容性无运行时库依赖问题有库版本冲突风险(DLL Hell)
性能函数调用无额外开销有极小的加载和间接调用开销

这里给大家展示一下链接动态库和静态库一个直观的区别:

因为大部分Linux服务器上是没有c语言或者c++的静态库的,所以我们得先进行安装静态库,在CentOS 7.9下的命令是:

sudo yum install -y glibc-static libstdc++-static

如果你是root用户可以去掉sudo

因为Linux系统中链接默认是链接动态库,所以这里我给大家补充一下链接静态库的命令:

假设我们的程序是test.c,现在要编译链接生成可执行文件program并且使用c语言的静态库

gcc -o program test.c -static

在这里插入图片描述


在这里插入图片描述

可以看到静态链接是十分浪费资源的,所以Linux服务器中默认使用的都是动态链接,而不是静态链接。这个资源不仅仅是磁盘空间,还有加载时的内存空间,以及上传下载时的网络资源。这个浪费还是很惊人的。

如何选择?

  • 选择静态库的情况

    • 程序需要分发到各种不确定的环境,希望做到开箱即用,避免依赖问题。(例如:发给用户的独立游戏、嵌入式系统)。
    • 你使用的库本身非常小,或者你不希望用户有机会替换这个库(出于安全或稳定性考虑)。
    • 对性能有极其苛刻的要求,不能容忍任何一点间接调用开销。
  • 选择动态库的情况

    • 程序很大,且依赖很多公共库(如GTK, Qt)。使用动态库可以极大减小磁盘和内存占用
    • 库需要被多个程序共享(如C标准库libc.so本身就是一个动态库)。
    • 库需要频繁更新或打补丁(如系统安全更新)。
    • 需要实现运行时插件系统

编译流程中库的链接

我们将结合动静态库,简单看看程序编译过程中的“链接”操作。


第一步:回顾编译流程与链接的位置

首先,我们明确“链接”在整个编译流程中的位置:(图中的ld 是 GNU 工具链中的链接器(linker),是编译流程最后阶段(链接阶段)的核心工具。)

flowchart TD
A[源代码<br>main.c lib.c] --> B[编译期 Compilation]
subgraph BB1[编译器 compiler<br>如gcc -c]
endB --> C[目标文件<br>main.o lib.o<br>(机器码片段, 未解析的符号)]C --> D[链接期 Linking]
subgraph Ddirection TBD1[链接器 linker<br>如ld]D2[静态库 lib.a]D3[动态库 lib.so]
endD2 & D3 --> D1D --> E[可执行文件<br>a.out 或.exe<br>(完整的可执行代码)]F[运行时] --> G[动态链接器 ld-linux.so<br>加载所需的动态库]
E --> F
D3 --> G

从上图可以看到,链接是编译过程的最后一步,它发生在所有源代码都被编译成目标文件(.o文件)之后。它的核心任务是:将所有零散的目标文件和所需的库文件“组合”成一个可以被操作系统加载执行的整体


第二步:链接器具体做了什么?(核心操作)

链接器的操作可以分为两个主要阶段,这与如何处理库文件密切相关。

阶段一:符号解析 (Symbol Resolution)
  • 目标:解决“谁是谁”的问题。
  • 什么是符号? 符号主要是指函数名和全局变量名。例如,你在main.c中调用了printf()printf就是一个符号。
  • 符号的分类
    • 符号定义 (Definition):一个函数的具体实现或一个全局变量分配了存储空间的地方。例如,printf的代码存在于libc.so中。
    • 符号引用 (Reference):调用一个函数或使用一个外部全局变量,但并未给出其具体实现的地方。例如,你的main.o中只有printf();这行调用指令,但没有printf的函数体。
  • 解析过程:链接器像侦探一样,扫描所有输入的目标文件(.o)和库文件(.a.so),为每一个符号引用找到与之对应的符号定义
    • 如果找不到定义,就会抛出经典的 undefined reference to '函数名' 错误。
    • 如果找到多个同名定义(比如两个.o文件都定义了同一个全局变量),就会抛出 multiple definition of '变量名' 错误。

库文件在符号解析中的角色

  • 静态库(.a)和动态库(.so)在链接时都是提供符号定义的仓库。
  • 链接器会根据你的编译命令(-lmath)去指定的库中寻找未解析的符号的定义。

补充:

在 Linux 编译场景中,-lmath(字母 l + 单词 math 是用于链接 数学函数库(libm) 的编译选项,核心作用是让编译器找到并关联程序中调用的数学函数(如三角函数、指数函数等),确保程序能正常编译和运行。

两个关键概念

在解释 -lmath 前,需要先明确 Linux 下 “数学库” 的特殊地位:

  1. C 标准库(libc)
    C 语言的基础库(如 printfscanfstrcpy 等函数),编译器(如 gcc)会自动链接,无需手动指定选项。
  2. 数学库(libm)
    包含数学相关的函数(如 sincossqrtpow 等),它不属于 C 标准库的自动链接范围,必须通过编译选项手动指定链接 —— 这就是 -lmath 的作用。

1. 选项语法规则

-l(小写字母 L)是 Linux 编译器(gcc/g++)的 “库链接选项”,语法为:
-l<库名> → 编译器会自动在库文件名前加 lib、后加 .a(静态库)或 .so(动态库),并在系统默认库路径中搜索该库。

对于 -lmath
-lmath → 编译器实际搜索的是 libmath.a(静态数学库)或 libmath.so(动态数学库)。
但在绝大多数 Linux 发行版(如 Ubuntu、CentOS、Debian)中,libmath.so/libmath.alibm.so/libm.a 的软链接(历史兼容原因),因此 -lmath 等价于更常用的 -lm(效果完全一致)。

2. 为什么需要 -lmath

如果程序中调用了数学函数(如 sqrt(2.0)sin(3.14)),但编译时未加 -lmath-lm,编译器会报 “未定义引用(undefined reference)” 错误—— 因为编译器找不到这些函数的实现(它们在 libm 中,而非默认链接的 libc 中)。

阶段二:重定位 (Relocation)
  • 目标:解决“在哪”的问题。
  • 问题来源:编译器和汇编器在生成目标文件(.o)时,并不知道每条指令、每个变量最终在内存中的绝对地址。它们只能从零地址开始生成代码。
  • 过程
    1. 合并与分配:链接器将所有目标文件中的相同节(Section)(例如:代码节.text、数据节.data)合并在一起,并为它们分配最终的运行时内存地址。
      • 例如,它将main.o.textlib.o.text和从静态库libmath.a中提取出来的add.o.text全部合并到输出文件的.text段,并告诉它们:“你们的代码从现在开始位于地址0x400500”。
    2. 修改引用:链接器然后遍历所有指令,将之前临时使用的地址(通常是相对于本文件开头的偏移量)全部修改为刚刚分配好的绝对地址。
      • 例如,在main.o中有一条指令call <printf的偏移量>。链接器知道printf的实际地址是0x400710后,就会将这条指令修改为call 0x400710

库文件在重定位中的角色

  • 静态库(.a:链接器会从静态库中提取出那些被用到的目标文件(比如add.o),将这些文件的代码和数据完整地复制到最终的可执行文件中,然后参与后续的重定位过程。最终的可执行文件是自包含的。
  • 动态库(.so:链接器不会复制动态库的代码!它只会做两件事:
    1. 记录下这个可执行文件依赖于哪个动态库(例如libc.so.6)。
    2. 对动态库中的符号,链接器会生成一些“占位符”信息(在Linux上,这涉及到过程链接表PLT和全局偏移表GOT),告诉运行时动态链接器:“这个函数的位置,你到时候再帮我填上去”。

第三步:结合动静态库的链接过程详解

现在我们把符号解析和重定位结合起来,看一个具体的例子。

假设: main.c 调用了 math.h 中的 add 函数。
编译命令: gcc main.c -L. -lmath -o calculator

场景一:libmath.a (静态库) 存在时的链接过程
  1. 输入文件:链接器接收 main.olibmath.a
  2. 符号解析
    • 链接器发现 main.o 中有一个未解析的符号 add
    • 它开始按顺序扫描输入文件。首先在 main.o 中找不到 add 的定义。
    • 然后它扫描 libmath.a。这个静态库实际上是 add.osubtract.o 的打包集合。
    • 链接器在 libmath.a 中的 add.o 里找到了 add 函数的定义。好,符号解析成功!
  3. 提取与重定位
    • 由于 add 被用到了,链接器会将 add.o libmath.a中提取(复制)出来
    • 链接器将 main.o 和提取出的 add.o 进行合并,为它们的代码和数据段分配最终的内存地址。
    • 链接器将 main.ocall add 的指令地址,修正为 add 函数实际的绝对地址。
  4. 输出:生成一个完整的、自包含的 calculator 可执行文件。这个文件内部已经包含了 add 函数的所有代码。
场景二:libmath.so (动态库) 存在时的链接过程
  1. 输入文件:链接器接收 main.olibmath.so
  2. 符号解析
    • 过程与静态库类似:链接器在 main.o 中找不到 add,最终在 libmath.so 中找到了 add 的定义。符号解析成功!
  3. 记录依赖与生成占位符
    • 关键区别:链接器不会add 的代码从 libmath.so 中复制出来。
    • 它只是在最终生成的 calculator 可执行文件中写入一条记录:“我依赖于动态库 libmath.so”。
    • 同时,它会在可执行文件中创建一个小数据结构(如PLT),里面写着:“add 这个函数,请在未来运行时,从 libmath.so 里找它的地址”。
  4. 输出:生成一个不完整的、依赖外部库calculator 可执行文件。这个文件很小,因为它不包含 add 的代码。
场景三:动静态库同时存在,链接器如何选择?

规则:默认优先选择动态链接!

如果链接器在同一个目录下既找到了 libmath.so 又找到了 libmath.a,它会优先选择链接动态库 libmath.so

如果你想强制使用静态库,有几种方法:

  1. 指定全路径gcc main.c /path/to/libmath.a -o calculator
  2. 使用编译选项
    • -static强制所有库都使用静态链接,生成一个完全静态的可执行文件。
    • -Wl,-Bstatic-Wl,-Bdynamic精细控制。例如:
      gcc main.c -Wl,-Bstatic -lmath -Wl,-Bdynamic -lc -o calculator
      
    • -Wl,-Bstatic 告诉链接器:“后面的库用静态链接”,所以 -lmath 会链接 libmath.a
    • -Wl,-Bdynamic 告诉链接器:“后面的库恢复默认的动态链接”,所以 -lc(C标准库)会链接 libc.so

总结:链接操作的核心思想

链接器是一个“解决问题的专家”:

  1. 解决符号依赖:为每个未定义的符号找到它的家(定义所在的目标文件或库)。
  2. 分配最终地址:将所有零散的代码和数据片段,整合到一个统一的地址空间中,并修正所有地址引用。
  3. 智能处理库
    • 静态库,它采用“按需取用”的策略,只用到的才提取并打包进最终文件。
    • 动态库,它采用“延迟绑定”的策略,只建立依赖关系,把真正的链接工作推迟到程序运行时。

简单类比一下生活中的场景:

动态链接:你(目标文件)想去健身(库函数),然后你被告知你家往东走1000米有家健身房,并知道了健身房的位置(动态库)(这个过程就是链接),然后在你想去健身的时候(运行的时候)就去那个健身房(去健身房的地址处)

静态链接:同样是健身,你家附近没健身房,但是有卖健身器材的(静态库),然后你就买了你需要的健身器材回家(将静态库中的代码复制到目标文件上)。

买健身器材回家会减少你家的空闲面积(空间消耗大),但是去健身房就不会有这个问题,但是你每次去健身房都要走1000米甚至还得开会员卡(这是链接动态库时的损耗)而且你得保证你家附近一定得有一个健身房(确保目标机器上安装了所有所需版本的动态库)否则健身这个活动就不成立了。


文章转载自:

http://UDVUkbXN.bsbcp.cn
http://RBiuwFUp.bsbcp.cn
http://Zo0xY4BT.bsbcp.cn
http://G6huyiNO.bsbcp.cn
http://AAl3X6MX.bsbcp.cn
http://qJU2tVY1.bsbcp.cn
http://yt0qFA6J.bsbcp.cn
http://rOTylgdo.bsbcp.cn
http://2evZoRGp.bsbcp.cn
http://fzAPsgCS.bsbcp.cn
http://Rxr41rjZ.bsbcp.cn
http://RnRCdnPW.bsbcp.cn
http://31SxGh6x.bsbcp.cn
http://IQE43Owo.bsbcp.cn
http://gkiglPqt.bsbcp.cn
http://jyU6qNIo.bsbcp.cn
http://YvhTCXt2.bsbcp.cn
http://3mzsvPqT.bsbcp.cn
http://MmetnIxL.bsbcp.cn
http://e41dshXJ.bsbcp.cn
http://pDN0Yasu.bsbcp.cn
http://sPdFDLao.bsbcp.cn
http://irq6fVxD.bsbcp.cn
http://5HZrRyld.bsbcp.cn
http://ZTuMnWNX.bsbcp.cn
http://F2eJPhCu.bsbcp.cn
http://ofpkSDHi.bsbcp.cn
http://xa73zT31.bsbcp.cn
http://IBSBpK2C.bsbcp.cn
http://QAKUwYCU.bsbcp.cn
http://www.dtcms.com/a/380674.html

相关文章:

  • EPC企业如何通过数字化管理提高盈利能力?
  • P2678 [NOIP 2015 提高组] 跳石头
  • 旋转位置编码的论文阅读
  • UE5 基础应用 —— 08 - 动画蓝图 简单使用
  • unity pcd 二进制版 简单显示文件对象(单色)
  • 面试题:Redis要点总结(复制、哨兵、集群)
  • Leetcode 18 java
  • Redis集群为何采用16384个槽的设计?
  • 《树与二叉树详解:概念、结构及应用》
  • Certimate SSL证书自动申请部署
  • 《Spring事务的失效》
  • Maya绑定:小球挤压拉伸变形详细绑定(晶格、簇、测量工具、节点编辑器)
  • 【比亚迪璇玑架构深度解析:重新定义智能电动汽车的“整车智能”】
  • jdbc DAO封装及BaseDAO工具类
  • jajajajajajajava
  • 自动生成链接
  • LeetCode 3258.统计满足K约束的子字符串数量 I
  • “量子能量泵”:一种基于并联电池与电容阵的动态直接升压架构
  • fastapi 使用本地资源自定义swagger文档
  • Vue FullPage.js 完整使用指南:Vue 3 官方全屏滚动解决方案
  • ARM IRQ中断
  • Ruoyi-vue-plus-5.x第八篇文件管理与存储: 8.2 OSS云存储集成
  • 解决:NVIDIA-SMI couldn‘t find libnvidia-ml.so library in your system.
  • 【LLM】VLLM:容器运行 ModelScope 模型
  • HarmonyOS 应用开发深度解析:基于 Stage 模型与 ArkUI 的跨组件状态共享最佳实践
  • TOGAF——战术性调整,战略性变更
  • 【计算机 UTF-8 转换为本地编码的含义】
  • 当人工智能遇上知识检索:RAG技术的深度解析与实践探索
  • 在线商城管理系统功能清单的系统设计
  • SLAM 系统设计是如何保证前端(tracking/VO)和后端(优化/BA/图优化)如何同步实时性思路汇总思考