【Linux】动态库、静态库使用与原理

1. 什么是库

库是写好的、现有的、成熟的、可以复用的代码。每个程序都依赖很多底层库,库的存在使得开发不必从零开始。

本质:库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。

库的两种类型

  • 静态库.a(Linux)、.lib(Windows)

  • 动态库.so(Linux)、.dll(Windows)

2. 静态库

2.1 特点

  • 静态库(.a)在程序编译链接时 把库的代码直接拷贝到可执行文件中。

  • 程序运行时不再需要静态库。

  • 可执行文件体积较大,但部署简单(不依赖外部库)。

2.2 静态库的本质

库的本质 :无论是静态库还是动态库,其基础都是源文件编译后生成的 .o 目标文件。静态库(.a)就是多个 .o 文件打包成一个归档文件。使用 gcc/g++ 链接时,链接器会从归档中提取需要的模块合并到最终可执行文件中。

静态库中不包含 main 函数,库只提供可复用的功能模块,不包含程序入口。

2.3 制作静态库

使用 ar(GNU归档工具)将多个 .o 文件打包成 .a 文件。

示例 :将 my_stdio.cmy_string.c 封装成静态库 libmystdio.a

Makefile 片段:

makefile 复制代码
libmystdio.a: my_stdio.o my_string.o
	ar -rc $@ $^
%.o: %.c
	gcc -c $<
  • ar -rcr 表示插入或替换文件,c 表示创建归档文件。

  • 查看静态库内容:ar -tv libmystdio.at 列出文件,v 显示详细信息)。

2.4 使用静态库

编译时需指定头文件路径、库路径和库名。

  • 头文件搜索路径-I 选项

  • 库文件搜索路径-L 选项

  • 库名-l 选项(去掉前缀 lib 和后缀 .a/.so

使用场景示例:

bash 复制代码
# 场景1:头文件和库文件已安装到系统路径(如 /usr/lib, /usr/include)
$ gcc main.c -lmystdio

# 场景2:头文件和库文件在当前目录
$ gcc main.c -L. -lmystdio

# 场景3:头文件和库文件在独立路径
$ gcc main.c -I头文件路径 -L库文件路径 -lmystdio

注意:静态库链接后,可执行文件不再依赖该库,即使删除静态库,程序仍可运行。

3. 动态库

3.1 特点

  • 动态库(.so)在程序运行时才被加载到内存,多个程序可以共享内存中的同一份库代码。

  • 可执行文件只包含函数入口地址的引用,体积小。

  • 节省磁盘和内存空间,但部署时需要确保运行时能找到动态库。

库:应用程序 = 1:n(一份物理内存中的动态库可被多个进程共享)。

3.2 制作动态库

生成位置无关码(-fPIC)并链接为共享库(-shared)。

Makefile 片段:

复制代码
libmystdio.so: my_stdio.o my_string.o
	gcc -o $@ $^ -shared
%.o: %.c
	gcc -fPIC -c $<
  • -fPIC:生成位置无关代码,使得动态库可以被加载到任意内存地址。

  • -shared:生成共享库。

3.3 使用动态库

编译命令与静态库类似:

bash 复制代码
$ gcc main.c -L. -lmystdio

生成的可执行文件 a.out 在运行时需要找到 libmystdio.so

3.4 运行时动态库查找问题

使用 ldd a.out 查看可执行文件依赖的共享库:

bash 复制代码
$ ldd a.out
linux-vdso.so.1 => (0x...)
libmystdio.so => not found
libc.so.6 => /lib64/libc.so.6
/lib64/ld-linux-x86-64.so.2

出现 not found 表示运行时找不到动态库。

关键理解 :编译时用 -L-l 只告诉了编译器(gcc)库的位置,但操作系统(动态链接器)并不知道。系统 != gcc,运行时需要另外告诉系统库的路径。

3.5 解决方案

  1. 拷贝库到系统库路径

    /usr/lib/usr/local/lib/lib64 等(需 root 权限)。

  2. 创建软链接

    在系统库路径下创建同名软链接指向实际库文件。

  3. 设置环境变量 LD_LIBRARY_PATH

bash 复制代码
export LD_LIBRARY_PATH=库所在目录:$LD_LIBRARY_PATH
  1. 配置 /etc/ld.so.conf.d/ 并运行 ldconfig
    /etc/ld.so.conf.d/ 下创建一个 .conf 文件,写入库路径,然后运行 ldconfig 更新缓存。
bash 复制代码
$ sudo cat /etc/ld.so.conf.d/bit.conf
/root/tools/linux
$ sudo ldconfig

4. 编译链接的默认行为

  • 结论1gcc/g++ 默认使用动态库。如果同时存在同名静态库和动态库,优先链接动态库。

  • 结论2 :若想强制静态链接,需加 -static 选项,此时必须存在对应的静态库。如果只存在静态库,则只能静态链接。

  • 结论3 :Linux 系统默认安装的库通常优先提供动态库(.so),静态库(.a)可能需要单独安装(如 -devel 包)。

5. 可执行程序的加载过程(简述)

  1. 操作系统读取可执行文件的头部信息,获取程序入口地址。

  2. 根据文件中的段信息,将代码段、数据段等映射到虚拟内存的合适位置。

  3. 对于动态链接的可执行文件,还需加载动态链接器 (如 /lib64/ld-linux-x86-64.so.2)。

  4. 动态链接器负责加载所需的共享库(.so),完成符号重定位(将库函数调用地址修正为实际内存地址)。

  5. 最后跳转到程序入口开始执行。

6. 虚拟地址空间与动态库加载

  • 每个进程拥有独立的虚拟地址空间(例如 32 位系统为 4GB,其中内核占用一部分)。

  • 动态库被加载到虚拟地址空间的共享区域,通过页表映射到物理内存。

  • 由于使用了位置无关代码-fPIC),动态库可以被加载到任意地址而不需修改代码,从而实现多个进程共享同一份物理内存中的库。

7. 总结

  • 静态库 :链接时合并到可执行文件,运行时独立,生成方式 ar -rc

  • 动态库 :运行时加载,节省资源,生成方式 gcc -shared -fPIC

  • 库的查找 :编译时用 -L-l,运行时依赖动态链接器搜索路径(系统目录、LD_LIBRARY_PATHldconfig 配置)。

  • 加载过程:操作系统将程序映射到虚拟内存,动态链接器完成共享库的加载和符号重定位。

  • 虚拟内存:使动态库能在物理内存中只保留一份副本,被多个进程共享。

相关推荐
MSTcheng.1 小时前
【Linux学习】Linux学习第四弹——Linux基本指令3
linux·windows·学习·操作系统·指令
raindayinrain1 小时前
c++并发--同步
c++·同步
我不是程序员三三1 小时前
怎么监控电脑?企业办公终端监控建设实战与合规要点
服务器·数据库·电脑
筝筝ba2 小时前
OpenBMC 自动化测试中的多进程陷阱:从 Execute Process 返回异常到 output.xml 损坏问题分析
xml·linux·运维·服务器·网络
饕餮争锋2 小时前
PATH路径列表介绍
java·linux·服务器
V158897262012 小时前
从滴滴模式看机器人租赁:撮合型租赁平台开发源码的调度系统设计思路
linux·前端·机器人
伯恩bourne2 小时前
Qdrant 快速入门 :数据模型介绍
服务器·数据库·人工智能
蛋先生DX2 小时前
内存安全翻车现场的罪魁祸首,根源就在这三步里?
c++·后端·编程语言
z落落2 小时前
C#简易聊天室(服务器+客户端)
运维·服务器·c#