首先 我们要明白什么是库?
库(Library)是一组预编译的代码,提供特定的功能,可以被多个程序共享调用,避免重复编写代码。在链接步骤中,链接器将从库文件取得所需的代码,复制到生成的可执行文件中
Linux 中常见的库文件有两种,一种.a为后缀,为静态库,另一种以.so为后缀,为动态库。
静态库 :在程序编译时,将库的代码"拷贝"到最终的可执行文件中;
动态库(共享库):在程序运行时,动态载入库的代码,多个程序可以共享同一份库文件。
静态库
静态库通常以.a为后缀(在Linux下),是多个目标文件(.o)组成的归档文件(Archive),里面存储着一组目标文件。
特点
- 在链接(Linking)的时候被合并到可执行文件中
- 可执行文件较大,因为库的代码已经复制到程序中
- 加载快,无需在运行时再载入库文件
- 无需在运行环境中存在库文件,便于部署
作用
- 方便将多个目标文件打包成一个完整的可执行程序
- 使用简单,不依赖外部共享库
创建静态库
bash
# 先编译源文件生成目标文件
gcc -c file1.c -o file1.o
gcc -c file2.c -o file2.o
# 将目标文件打包为静态库
ar rcs libmylib.a file1.o file2.o
# 编译时链接静态库
gcc main.o -L. -lmylib -o myprogram
使用静态库
- 在编译时指定库路径和库名(-L和-l参数)
- l:指定库名(库的文件名为 libxxx.a,库名为 xxx)
- L:指定库路径
- static:使用静态链接
bash
gcc -static main.c -o main -l math -L ./
// 错误:gcc -l math -static main.c -o main -L ./
特别注意,必须把 -l math 放在后面。放在最后时它是这样的一个解析过程:
- 链接器从左往右扫描可重定位目标文件和静态库
- 扫描 main.c 时,发现两个未解析的符号 add 和 sub,记住这两个未解析的符号
- 扫描 libmath.a,找到了前面未解析的符号,因此提取相关代码
- 最终没有任何未解析的符号,编译链接完成
那如果将 -l math 放在前面,又是怎样的情况呢? - 链接器从左往右扫描可重定位目标文件和静态库
- 扫描 libmath.a,由于前面没有任何未解析的符号,因此不会提取任何代码
- 扫描 main.c,发现未解析的符号 add 和 sub
- 扫描结束,还有两个未解析的符号,因此编译链接报错
如果把-l math放在前面,编译结果如下:

我们看看最终生成的文件大小:
生成可执行文件大小为892k
由于最终生成的可执行文件中已经包含了add和sub相关的二进制代码,因此这个可执行文件在一个没有libmath.a的Linux系统中也能正常运行。
动态库
动态库和静态库类似,但是它并不在链接时将需要的二进制代码都"拷贝"到可执行文件中,而是仅仅"拷贝"一些重定位和符号表信息,这些信息可以在程序运行时完成真正的链接过程。Linux 中这类库的名字一般是 libxxx.so。(shared object)
符号表
- 符号表记录了函数和变量的名字、地址等信息,是连接器、加载器用来解决引用关系的重要数据结构。
- 在动态库中,符号表包括:
- 导出符号:库中提供给外部调用的函数和变量
- 需要被其他程序或库引用的符号
重定位信息
- 重定位是指在程序或库加载到内存后,动态调整代码中的地址引用,使得符号指向正确的内存地址。
- 重定位信息记录了哪些位置需要修正(如函数调用、全局变量访问位置)
- 在静态链接中,重定位在链接阶段完成;在动态链接中,加载时由系统完成。
定义:
- 动态库也称"共享库",以.so(Shared Object)为后缀
- 在程序运行时动态载入,多个程序可以共享同一份库文件
特点
- 在程序加载时才载入库(动态链接)
- 可减小可执行文件大小,因为库在运行时加载
- 便于维护和升级,只需替换库文件,无需重新编译所有程序
- 运行时依赖,程序必须找到对应的.so文件
作用
- 方便库的共享和维护
- 可以实现插件式开发或模块化设计
创建动态库
bash
# 编译生成共享库
gcc -fPIC -c file1.c -o file1.o
gcc -fPIC -c file2.c -o file2.o
gcc -shared -o libmylib.so file1.o file2.o
# 编译程序链接动态库
gcc main.o -L. -lmylib -o myprogram
注意:
- -fPIC:生成位置无关代码(Position Independent Code)
- -shared:生成共享库
使用动态库
步骤 | 命令示例 | 说明 |
---|---|---|
1. 创建共享库 | gcc -fPIC -c mylib.c -o mylib.o gcc -shared -o libmylib.so mylib.o |
生成.so 文件 |
2. 编译调用程序 | gcc main.c -L. -lmylib |
连接到共享库 |
3. 设置环境变量或放库到系统路径 | export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH sudo cp libmylib.so /usr/lib/ |
让运行时找到共享库 |
4. 运行程序 | ./myprogram |
运行程序,调用共享库中的函数 |
找不到的原因
原因类别 | 描述 | 解决措施 |
---|---|---|
库路径未在标准路径或未指定 | 库文件所在路径未加入系统路径或未在运行时指定 | 设置LD_LIBRARY_PATH 或rpath |
缺少ldconfig 刷新缓存 |
添加新库后未刷新系统缓存 | 运行ldconfig |
编译参数不正确 | 编译时未设置-rpath 或路径错误 |
使用-Wl,-rpath 指定路径 |
依赖缺失 | 库依赖的其他库不存在或路径错误 | 用ldd 检测,安装缺失的库 |
权限不足 | 库文件权限限制 | 修改文件权限 |
主要原因是系统在运行时无法定位到库文件的存放路径,可能因为库路径没有配置正确、库文件放错位置、未刷新缓存或依赖缺失等。
当在运行程序时,动态库(.so文件)找不到,系统会报错,导致程序无法正常启动。这种情况常见的错误信息有:
bash
error while loading shared libraries: libmylib.so: cannot open shared object file: No such file or directory
动态库找不到时的解决办法
1.临时设置环境变量LD_LIBRARY_PATH
这是一种快速方便的方式,临时告诉系统去哪里找共享库。
bash
export LD_LIBRARY_PATH=.:/path/to/lib:$LD_LIBRARY_PATH
./your_program
示例:假设libmylib.so在当前目录:
bash
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./your_program
缺点:此设置只在当前终端有效,关闭终端后失效。
2.将共享库拷贝到系统标准目录
将你的共享库复制到系统的库目录(如/usr/lib或/usr/local/lib),然后刷新缓存。
bash
sudo cp libmylib.so /usr/lib/
sudo ldconfig
说明:
ldconfig命令会刷新系统的库缓存,让系统知道新加入的库。
注意:需要管理员权限。
3.修改程序的RPATH或RUNPATH(在编译时指定)
在编译时,将库的路径提前嵌入到可执行文件中,使其在运行时自动查找。
例:
bash
gcc main.c -L/path/to/lib -Wl,-rpath=/path/to/lib -lmylib -o myprogram
- -Wl,-rpath=路径:在可执行文件中指定库搜索路径
执行后,无需设置LD_LIBRARY_PATH,系统会自动在指定路径查找库。
4.使用LD_LIBRARY_PATH环境变量文件(配置全局)
编辑/etc/ld.so.conf或在/etc/ld.so.conf.d/目录中添加自定义路径,然后运行
bash
sudo ldconfig
这样,系统会在指定路径中查找库。
5. 动态调试:使用ldd命令检测缺失的依赖库
运行:
bash
ldd ./your_program
可以显示程序依赖的所有共享库,查看是否有"not found"的库。
如果发现缺失的库,可以用上面的方法解决。
小结
方法 | 作用 | 适用场景 |
---|---|---|
LD_LIBRARY_PATH |
临时指定库路径 | 调试或快速测试 |
拷贝到系统目录 | 将库放到标准路径,系统自动加载 | 部署时,保证程序可以找到库 |
RPATH/RUNPATH | 在编译时嵌入路径 | 内部控制,避免环境变量设置 |
ldconfig 配置 |
全系统范围配置库搜索路径 | 长期维护或系统级别的配置 |
ldd 命令检测依赖 |
查看依赖关系,找出缺失的库 | 调试、排错 |
静态库与动态库的区别
特性 | 静态库(.a ) |
动态库(.so ) |
---|---|---|
链接时间 | 编译时(静态链接) | 运行时(动态链接) |
生成文件大小 | 较大(所用到代码要从二进制文件中拷贝一份,复制到可执行文件中) | 较小(库文件单独存在,仅仅复制了一些重定位和符号表信息) |
占用空间 | 运行时每个程序都持有一份库的副本 | 多个程序共享同一份库(内存节省) |
升级维护 | 需要重新编译程序 | 只需替换库文件,无需重新编译 |
依赖关系 | 不需要库文件存在于运行环境中 | 需要库文件存在于运行环境中 |
运行速度 | 略快(没有动态加载耗时) | 因为需要动态加载,可能略慢 |