Linux中动静态库的理解
软硬链接
硬链接
ln a b,就是将目标文件a硬链接到文件b
ls -i 查看文件的inode,ls -li查看所有文件的inode
原理
Linux 文件由inode + 数据块组成:
- inode:记录文件元信息(权限、大小、指向数据块指针),文件名只是 inode 的一个别名。
- 硬链接本质:多个文件名指向同一个 inode
特点
- 同一个 inode 号,多个文件名,共享磁盘数据
- 只能对文件创建,不能给目录创建硬链接(系统目录本身有. .. 硬链接除外)
- 不能跨文件系统(分区),inode 只在当前文件系统有效
- 删除一个硬链接,只是删掉一个文件名;只要还有一个链接存在,文件数据不会删除
- 修改任意一个硬链接文件,所有链接文件都会同步变化(共享同一份数据)
- 不额外占用磁盘空间(仅增加目录项记录)

创建并写入内容到test.txt,观察inode
软连接
ln -s a b,将文件a软链接到文件b
test.txt 和 hard.txt inode 相同;soft.txt 是独立 inode
原理
软链接本身是独立的小文件,它的 inode 指向的数据内容保存的是源文件路径。类似 Windows 的快捷方式。
特点
- 软链接有自己独立的 inode,是一个单独文件
- 可以链接文件,也可以链接目录
- 支持跨分区、跨文件系统
- 如果源文件删除,软链接依然存在,但变成失效链接(红闪烁),访问会提示 No such file
- 修改软链接对应的文件,实际修改的是源文件数据
- 软链接文件本身占用少量磁盘空间(存放路径字符串)
- 相对路径创建软链接时,源路径是相对于软链接所在目录,不是当前执行命令目录,很容易踩坑
使用场景
- ✅ 硬链接:重要文件多备份,防止误删(同一分区内)
- ✅ 软链接:版本切换(python 多版本)、目录迁移、快捷访问、跨分区链接
| 特性 | 硬链接 | 软链接 | |||
| inode | 和源文件相同 | 独立 inode | |||
| 本质 | 文件名别名 | 保存路径的独立文件 | |||
| 支持目录 | ❌ 不支持 | ✅ 支持 | |||
| 跨分区 | ❌ 不支持 | ✅ 支持 | |||
| 删除源文件 | 文件还在 | 链接失效 | |||
| 文件大小 | 和源一致 | 路径字符串长度 | |||
| 权限 | 和源一致 | 软链接权限几乎无效,以源文件为准 | |||
动静态库
动态库
库:库是写好的现有的,成熟的,可以直接复用的代码实现,不需要去写代码时需要应用到然后在去重写代码,直接调用库中的代码应用就可以。
动态库(.so)
原理:链接阶段只记录库的符号信息,不拷贝代码;程序运行时才加载动态库到内存。多个进程可以共享内存里同一份库代码。
特点:
✅ 优点
- 可执行文件小,内存中只加载一份库,节省内存
- 库升级:替换
.so文件,程序不用重新编译(接口不变即可)
❌ 缺点
- 运行环境必须存在对应
.so,否则程序无法启动(经典:error while loading shared libraries) - 运行时多一步加载,速度稍慢
# 1. 生成位置无关代码 -fPIC(必须!)
gcc -c -fPIC add.c -o add.o
# 2. 生成动态库 libxxx.so
gcc -shared add.o -o libadd.so
# 3. 编译程序链接动态库
gcc main.c -o main -L. -ladd
创建及使用
add.h
#ifndef __ADD_H__
#define __ADD_H__
int add(int a,int b);
#endif
add.c
#include "add.h"
int add(int a,int b)
{
return a+b;
}
main.c
#include <stdio.h>
#include "add.h"
int main(void)
{
printf("10+20=%d\n", add(10,20));
return 0;
}
第 1 步:生成位置无关代码 -fPIC
gcc -c -fPIC add.c -o add.o
-c:只编译,不链接,生成目标文件 add.o-fPIC:位置无关代码,动态库必须加这个参数!
动态库会被加载到内存任意地址,代码不能写死内存地址,所以要 PIC。不加这个编译会报错!
第 2 步:用 add.o 打包生成动态库 libadd.so
gcc -shared add.o -o libadd.so
-shared:告诉 gcc 生成共享库(动态库)- 命名规则:
libxxx.so,xxx 是库名,后面-lxxx使用
执行完当前目录就有
libadd.so
③ 3-2 动态库使用(编译 main 程序链接动态库)
gcc main.c -o main_dynamic -L. -ladd
解释:
-L.:编译时,在当前目录找库文件(libadd.so)-ladd:链接 libadd.so(自动省略 lib 和 .so)
✅ 到这里,编译成功,生成可执行文件 main_dynamic
问题
现在直接运行:
./main_dynamic
报错:error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory
❗核心概念区分(很多人搞不懂)
-L.只是编译阶段给 gcc 看的:编译的时候去当前目录找库 程序运行阶段,操作系统的动态链接器 ld-linux.so 不会默认去当前目录找 so! 编译成功 ≠ 运行成功!
用 ldd 查看程序依赖的动态库:
ldd ./main_dynamic
你会看到:libadd.so => not found
4 种解决方案(由简单到永久)
方案 1:临时环境变量 LD_LIBRARY_PATH(课堂测试首选,临时生效)
# 把当前目录加入动态库搜索路径
export LD_LIBRARY_PATH=$PWD
# 再运行程序
./main_dynamic
✅ 输出:10+20=30
缺点:只在当前终端有效,新开终端就失效。
方案 2:编译时写入 rpath(推荐实验用,把路径写进程序内部)
重新编译 main,加上 -Wl,-rpath=$PWD
gcc main.c -o main_dynamic -L. -ladd -Wl,-rpath=$PWD
-Wl,-rpath:传递参数给链接器,把库路径硬编码到程序中 之后直接运行./main_dynamic,不用设置环境变量。
方案 3:修改系统配置文件(全局永久)
- 将库路径写入
/etc/ld.so.conf
sudo echo "/root/lesson10" >> /etc/ld.so.conf
- 更新系统动态库缓存
sudo ldconfig
所有用户、所有终端都可以找到这个 so,永久生效。
方案 4:直接拷贝 so 到系统库目录
sudo cp libadd.so /usr/lib
系统默认搜索 /usr/lib,拷贝完成即可运行。
静态库
原理
编译链接阶段,把库里面的代码完整复制,合并进可执行文件。运行时不再依赖原库文件。
特点
✅ 优点
- 运行独立,不需要系统上存在该库,移植方便
- 运行速度略快(运行时不用加载库)
❌ 缺点
- 可执行文件体积大,多个程序用同一个静态库,会有多份副本,浪费磁盘
- 库更新:必须重新编译链接程序才能生效
# 1. 源码编译成目标文件 .o
gcc -c add.c -o add.o
# 2. ar 打包成静态库 libxxx.a
ar rcs libadd.a add.o
# r:插入文件 c:创建库 s:建立索引
# 3. 使用静态库编译程序
gcc main.c -o main -L. -ladd
# -L 指定库搜索路径;-ladd 链接 libadd.a(省略lib和.a)
创建使用静态库
add.h
#ifndef __ADD_H__
#define __ADD_H__
int add(int a, int b);
#endif
add.c
#include "add.h"
int add(int a, int b)
{
return a + b;
}
main.c
#include <stdio.h>
#include "add.h"
int main(void)
{
int a = 10;
int b = 20;
printf("10 + 20 = %d\n", add(10,20));
return 0;
}
静态库生成
- 把
add.c编译成目标文件 add.o
gcc -c add.c -o add.o
- 使用
ar工具打包,生成静态库libadd.a
ar rcs libadd.a add.o
- r:插入 / 替换文件
- c:创建库文件
- s:生成符号索引,加快链接
编译 main,链接静态库:
gcc main.c -o main_static -L. -ladd
-L.:告诉编译器,库文件在当前目录查找-ladd:链接 libadd.a,自动补全lib和.a
运行:
./main_static
输出:10 + 20 = 30
✅ 静态库特点:编译完成后,删除
libadd.a,程序依旧可以正常运行。代码已经拷贝进可执行文件。
| 项目 | 静态库 .a | 动态库 .so | |
| 链接时机 | 编译链接期 | 运行期 | |
| 代码拷贝 | 完整复制进程序 | 只记录符号,不复制 | |
| 文件体积 | 大 | 小 | |
| 内存占用 | 多进程多副本 | 内存共享一份 | |
| 更新库 | 重新编译程序 | 替换 so 即可 | |
| 运行依赖 | 不依赖原库 | 必须存在 so 文件 | |
| 扩展名 | .a | .so | |
面试题
一、基础简答题(必背)
1. 什么是静态库?后缀?原理?
答: 静态库:.a(Linux),本质是一堆 .o 目标文件的归档包(ar打包)。 编译链接阶段,静态库里面的代码会直接复制拷贝到你的可执行程序中。 程序运行时不再依赖静态库文件。
优点:运行独立,不需要外部库;部署简单。 缺点:可执行文件体积大;多个程序同时使用会有多份代码副本,浪费内存;库更新后程序必须重新编译。
2. 什么是动态库(共享库)?后缀?原理?
答: 动态库:.so(Linux)。 链接阶段只记录符号信息,不拷贝代码;等到程序运行的时候,操作系统才把动态库加载进内存,多个进程可以共享同一份库代码。
优点:程序体积小;内存节省;库升级替换,程序不用重新编译。 缺点:运行时依赖 so 文件,缺少就无法运行;部署要带上库。
3. 静态库和动态库的区别(高频面试大题)
答:
- 链接时机不同 静态库:编译链接阶段,代码拷贝进可执行文件。 动态库:编译只做符号检查;运行时才加载库。
- 文件依赖 静态:编译完成后,
.a库文件可以删除,程序正常运行。 动态:运行时必须存在.so,否则程序启动失败。 - 内存占用 静态:多个进程运行,内存有多份库代码。 动态:内存中只保留一份库,多个进程共享。
- 程序大小 静态编译出来的可执行文件更大;动态更小。
- 更新方式 静态:库修改,应用必须重新编译。 动态:替换 so 文件,应用不用重新编译。
4. 静态库怎么生成、使用?
答:
#1.编译生成目标文件
gcc -c add.c -o add.o
#2.ar打包生成静态库libadd.a
ar rcs libadd.a add.o
#3.使用静态库编译程序
gcc main.c -o main_static -L. -ladd
-L.:编译时库搜索路径-ladd:链接 libadd 库
5. 动态库怎么生成、使用?为什么要 -fPIC?
答:
#1.生成位置无关代码
gcc -c -fPIC add.c -o add.o
#2.生成动态库
gcc -shared add.o -o libadd.so
#3.链接程序
gcc main.c -o main_dynamic -L. -ladd
-fPIC:Position Independent Code,位置无关代码。 动态库可以被加载到内存任意地址,代码不能写死绝对内存地址,必须生成 PIC 代码;静态库不需要。
原理 & 坑点题(你实验踩过的坑,面试最爱问)
6. 编译时 -L 和环境变量 LD_LIBRARY_PATH 的区别?
答:
-L:编译链接阶段生效,告诉 gcc 去哪里找库文件。LD_LIBRARY_PATH:程序运行阶段生效,告诉操作系统动态链接器去哪里找 so。
所以:编译成功 ≠ 运行成功。
7. 动态库编译成功,运行报错 cannot open shared object file,原因 + 解决方案?
答: 原因:编译时 -L 指定路径只给编译器用;运行时动态链接器不会默认去当前目录找 so。
四种解决办法:
- 临时设置环境变量:
export LD_LIBRARY_PATH=$PWD(当前终端生效) - 编译加
-Wl,-rpath,路径,把库路径写入程序内部 - 修改
/etc/ld.so.conf添加库路径,执行sudo ldconfig更新缓存(全局永久) - 将 so 拷贝到系统默认库目录
/usr/lib
8. 同一个目录下同时存在 libadd.a 和 libadd.so,执行 gcc main.c -L. -ladd,会链接静态还是动态库?怎么强制静态链接?
答: gcc 优先链接动态库 libadd.so。 强制静态链接:增加 -static 参数
gcc main.c -o main_static -L. -ladd -static
9. ldd 命令作用?
答: ldd 可执行文件名,查看这个程序依赖哪些动态库,以及库是否能找到。找不到会显示 not found。
10. ar 命令 rcs 参数含义?
答:
- r:替换 / 插入文件到库中
- c:创建库文件
- s:生成符号索引,加快链接速度
11. 动态链接的两种方式:隐式链接、显式链接(dlopen)
答: 隐式链接:就是我们刚才实验这种,编译时 -lxxx 链接库,程序启动自动加载 so。 显式链接:代码里面用 dlopen()、dlsym()、dlclose() 在代码里手动打开动态库,运行时随时加载,不需要编译时链接。插件化框架常用。
12. 动态库的 rpath 是什么?
答: rpath 是写入可执行程序内部的库搜索路径。程序运行时优先从 rpath 指定目录查找 so,不需要配置环境变量。编译参数:-Wl,-rpath=/xxx/path
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)