1. 程序库概述
1.1 库的基本概念
- 定义:库是预先编译好的二进制文件形式,包含了一系列函数接口。
- 作用:提供复用功能,类似标准库(Standard Library),开发者可以自定义库供他人调用。
- 形式:
- 源代码(
.c)经过编译打包后形成二进制库文件。 - 调用时直接调用打包好的库,而非直接使用
.c源码。
- 源代码(
- 常见类型:
- C 库:基础功能。
- 数学库:处理复杂数学问题。
- 线程库:处理多线程进程操作。
- 设计原则:
- 高复用性:库函数接口需要高度抽象,不依赖于特定应用环境,像一个“壳子”供任何人套用。
- 二进制形式:库文件通常是二进制形式,源码保密,无法还原。
1.2 库文件的位置与注意事项
- 系统路径:
- 静态库通常位于
/lib或/usr/lib。 - 动态库通常位于
/lib或/usr/lib。
- 静态库通常位于
- 文件后缀:
- 静态库:
.a(Archive)。 - 动态库:
.so(Shared Object)。
- 静态库:
- 警告:
- 系统库文件千万不要手动删除或修改,否则可能导致系统无法正常运行或调用失败。
- Windows 和 Linux 的库文件格式不兼容,不可混用。
2. 静态库 (Static Library)
2.1 静态库的特点
- 链接时机:在程序编译阶段将库代码拷贝并链接到可执行文件中。
- 优势:
- 执行速度快:运行时不需要额外加载库,代码已在程序内部。
- 独立性:生成的可执行文件不依赖外部库文件,移植方便。
- 不足:
- 体积冗余:每个使用该库的程序都会包含一份库代码拷贝,导致可执行文件体积变大,占用磁盘空间多。
- 维护困难:如果库函数需要升级,所有使用该库的程序都需要重新编译。
2.2 静态库的制作流程
- 编写源代码:
- 实现库函数(例如
hello.c),包含功能实现(如printf("Hello World"))。 - 确保包含必要的头文件(如
#include <stdio.h>)。
- 实现库函数(例如
- 生成目标文件 (
.o):- 命令:
gcc -c hello.c -o hello.o - 说明:只编译到汇编后的目标文件阶段,不生成可执行文件。
- 命令:
- 打包成静态库 (
.a):- 命令:
ar -rsv libhello.a hello.o - 命名规范:必须以
lib开头,以.a结尾(例如libhello.a)。 - 参数解释:
r:如果库存在则更新。s:生成符号表(Symbol Table),方便程序调用时查找。v:显示详细创建过程信息。
- 若有多个
.o文件,需全部列在命令后。
- 命令:
2.3 静态库的使用流程
- 声明接口:
- 在主程序(如
main.c)中声明库函数原型,或包含对应的头文件。
- 在主程序(如
- 编译链接:
- 命令:
gcc main.c -L. -lhello -o main - 参数解释:
-L.:指定库文件搜索路径为当前目录(.)。-lhello:指定库名称(省略lib前缀和.a后缀,编译器会自动查找libhello.a)。
- 命令:
- 运行:
- 直接执行
./main即可,无需额外环境配置。
- 直接执行
2.4 查看静态库信息
- 工具:
nm命令。 - 命令:
nm libhello.a - 作用:查看库中的符号表,了解库提供了哪些函数接口。
- 符号含义:
T:表示该符号是全局文本符号(代码段),说明函数代码已包含在库中。
3. 动态库 (Dynamic Library / Shared Library)
3.1 动态库的特点
- 链接时机:在程序运行阶段动态加载库代码。
- 优势:
- 节省空间:可执行文件体积小,不包含库代码。
- 内存共享:多个程序运行时可共享内存中的同一份库代码(共享动态库)。
- 易于维护:库文件升级只需替换
.so文件,主程序无需重新编译。
- 不足:
- 启动稍慢:运行时需加载库,增加少量启动时间。
- 依赖环境:运行环境必须存在对应的库文件,否则无法运行。
3.2 动态库的制作流程
- 生成位置无关代码目标文件:
- 命令:
gcc -fPIC -c hello.c -o hello.o - 关键参数:
-fPIC(Position Independent Code)。 - 原因:保证生成的代码在内存中加载位置无关,方便动态链接器在不同内存地址加载。
- 命令:
- 生成动态库文件 (
.so):- 命令:
gcc -shared -o libhello.so hello.o - 参数解释:
-shared:指定生成共享库。
- 命名规范:必须以
lib开头,以.so结尾(例如libhello.so)。
- 命令:
3.3 动态库的使用流程
- 编译链接:
- 命令:
gcc main.c -L. -lhello -o main - 说明:编译阶段告知编译器库的位置和名称(与静态库相同)。
- 命令:
- 运行时配置(关键步骤):
-
问题:直接运行
./main可能报错error while loading shared libraries: libhello.so: cannot open shared object file。 -
原因:编译器
-L参数仅用于编译阶段查找库;运行阶段动态链接器默认不去当前目录查找,而是查找系统环境变量指定的路径。 -
解决方法:设置
LD_LIBRARY_PATH环境变量。 -
临时设置命令:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)LD_LIBRARY_PATH:指定动态库搜索路径。$(pwd):获取当前绝对路径。::路径分隔符。
-
生效:设置后需执行
source命令或重新打开终端使环境变量刷新。 -
永久设置:可将 export 命令写入
~/.bashrc文件末尾。
-
3.4 查看动态库信息
- 工具:
nm命令。 - 命令:
nm libhello.so - 符号含义:
U:表示 Undefined(未定义)。- 原因:动态库在编译主程序时并未将代码拷贝进去,因此在主程序的符号表中,库函数显示为“未定义”,等待运行时加载解析。
- 依赖查看:
- 命令:
ldd main - 作用:查看可执行文件依赖哪些动态库。
- 命令:
4. 静态库与动态库对比总结
| 特性 | 静态库 (.a) |
动态库 (.so) |
|---|---|---|
| 文件后缀 | .a |
.so |
| 命名规范 | lib + 名字 + .a |
lib + 名字 + .so |
| 链接时间 | 编译链接时 (Compile Time) | 程序运行时 (Run Time) |
| 代码存在形式 | 代码拷贝进可执行文件内部 | 代码独立存在,运行时加载 |
| 可执行文件大小 | 较大 (包含库代码) | 较小 (仅含调用引用) |
| 内存占用 | 多程序运行时,每个程序独占一份库代码内存 | 多程序可共享内存中的同一份库代码 |
| 加载速度 | 启动快 (无需加载库) | 启动稍慢 (需加载库) |
| 库升级维护 | 困难 (需重新编译主程序) | 方便 (直接替换 .so 文件) |
| 依赖环境 | 无外部依赖,独立性强 | 依赖系统存在对应的 .so 文件 |
| 符号表特征 | nm 查看显示 T (Text/Code) |
nm 查看主程序显示 U (Undefined) |
| 适用场景 | 运行环境稳定、不需频繁更新、希望分发单一文件 | 需频繁更新库、节省内存、多个程序共享库 |
5. 关键命令与注意事项
5.1 常用编译与处理命令
- 编译目标文件:
gcc -c source.c -o target.o - 生成静态库:
ar -rsv libname.a target.o - 生成动态库:
gcc -shared -fPIC -o libname.so target.o - 链接库文件:
gcc main.c -L. -lname -o main-L:指定库路径。-l:指定库名(去掉lib和 后缀)。
- 查看符号表:
nm library_file - 查看动态依赖:
ldd executable_file - 设置环境变量:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/lib
5.2 开发规范与注意事项
- 大小写敏感:Linux 系统严格区分大小写,库文件名、命令参数均需准确。
- 命名冲突:同一路径下不能有同名的库文件,避免链接错误。
- 路径安全:
- 不要随意删除
/lib或/usr/lib下的系统库。 - 开发测试时尽量使用独立目录,避免污染系统环境。
- 不要随意删除
- 环境变量优先级:
- 运行时链接器优先查找
LD_LIBRARY_PATH指定的路径。 - 编译时的
-L参数不影响运行时查找路径。
- 运行时链接器优先查找
- 位置无关代码:
- 制作动态库必须加
-fPIC参数,否则可能无法正确加载或共享。
- 制作动态库必须加
5.3 故障排查
- 问题:运行时报
cannot open shared object file: No such file or directory。 - 解决:
- 检查库文件是否存在。
- 检查
LD_LIBRARY_PATH是否包含库文件所在目录。 - 使用
ldd命令检查可执行文件是否找到了库。
- 问题:编译时报
undefined reference to ...。 - 解决:
- 检查是否正确链接了库(
-l参数)。 - 检查库路径是否正确(
-L参数)。 - 检查函数声明是否匹配。
- 检查是否正确链接了库(