【Linux指南】动静态库系列:动态库运行报错排查:为什么编译通过,运行却找不到 .so

很多同学第一次做动态库时,会遇到一个非常典型的问题:

gcc main.c -I./include -L./lib -lmyc -o main

编译链接明明成功了,但一运行:

./main

却报错:

error while loading shared libraries: libmyc.so: cannot open shared object file: No such file or directory

或者用 ldd 查看时看到:

libmyc.so => not found

这个问题非常关键,因为它说明动态库有两个阶段都要“找库”:编译链接阶段要找,程序运行阶段还要再找。本文就专门把这件事讲透。

一、先复现问题
假设我们的目录结构如下:

shared_lib_demo/
├── include/
│ └── my_math.h
├── lib/
│ └── libmyc.so
└── main.c

main.c 调用了 libmyc.so 中的函数。

编译命令:

gcc main.c -I./include -L./lib -lmyc -o main

这条命令可能成功生成 main。

但是执行:

./main

可能报错:

./main: error while loading shared libraries: libmyc.so: cannot open shared object file: No such file or directory

这时候很多人会困惑:

编译的时候不是已经用 -L./lib 告诉 gcc 库在哪里了吗?
为什么运行的时候还说找不到?

答案是:

-L 只影响编译链接阶段,不负责程序运行阶段。

二、编译期找库和运行期找库不是一回事
动态库有两个重要阶段。

1. 编译链接阶段
命令:

gcc main.c -I./include -L./lib -lmyc -o main

这时参与工作的主要是编译器和链接器。

参数含义:

-I./include 告诉编译器去哪里找头文件
-L./lib 告诉链接器去哪里找库文件
-lmyc 告诉链接器链接 libmyc.so 或 libmyc.a

只要链接器在 ./lib 下找到了 libmyc.so,它就可以生成可执行程序。

2. 程序运行阶段
当你执行:

./main

此时不再是 gcc 在工作,而是操作系统加载程序,并由动态链接器负责加载依赖的 .so。

Linux 下常见动态链接器是:

/lib64/ld-linux-x86-64.so.2

或者 Ubuntu /Debian 上类似:

/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

动态链接器并不会自动记住你编译时写过的 -L./lib。所以如果它的搜索路径里没有 ./lib,运行时就找不到 libmyc.so。

这就是问题本质。

三、用 ldd 查看动态依赖
ldd 可以查看一个可执行程序依赖哪些动态库:

ldd ./main

如果库找不到,会看到:

linux-vdso.so.1
libmyc.so => not found
libc.so.6 => /lib64/libc.so.6
/lib64/ld-linux-x86-64.so.2

其中:

libmyc.so => not found

说明可执行程序确实记录了对 libmyc.so 的依赖,但动态链接器在运行搜索路径中没有找到它。

如果库能找到,则可能显示:

libmyc.so => ./lib/libmyc.so

或:

libmyc.so => /usr/local/lib/libmyc.so
所以排查动态库运行问题时,第一步通常就是:

ldd ./main

四、动态链接器默认去哪里找库
动态链接器运行时查找 .so,常见来源包括:

可执行文件中记录的 rpath/runpath。
环境变量 LD_LIBRARY_PATH 指定的路径。
/etc/ld.so.cache 缓存中的路径。
系统默认库目录,例如 /lib、/usr/lib、/lib64、/usr/lib64。
/etc/ld.so.conf 和 /etc/ld.so.conf.d/ 配置后经 ldconfig 生成的缓存路径。
不同系统、不同链接选项下的精确顺序会涉及一些细节。初学阶段先抓住核心:

运行时找库由动态链接器负责,和 gcc 编译时的 -L 不是一套机制。

五、解决方案一:拷贝到系统库路径
最直接的方法是把 .so 拷贝到系统默认库路径,例如:

sudo cp libmyc.so /usr/local/lib/
sudo ldconfig

或者某些系统中使用:

sudo cp libmyc.so /lib64/
sudo ldconfig

这样动态链接器就能从系统路径找到它。

但这个方案不适合学习实验中随便乱用,因为:

需要 root 权限。
容易污染系统库目录。
多版本库可能冲突。
删除不干净会影响后续实验。
所以学习阶段更推荐使用 LD_LIBRARY_PATH 或 rpath。

六、解决方案二:在系统路径建立软链接
如果真实库在某个自定义目录,可以在系统库路径中建立软链接:

sudo ln -s /home/user/mylib/libmyc.so /usr/local/lib/libmyc.so
sudo ldconfig

这样系统路径下看起来有 libmyc.so,实际文件仍在原位置。

软链接方案比直接拷贝更方便维护,但依然需要 root 权限,也需要注意不要污染系统目录。

七、解决方案三:设置 LD_LIBRARY_PATH
学习和临时测试最常用的是:

export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH
./main

如果库就在当前目录:

export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
./main

LD_LIBRARY_PATH 是环境变量,用来告诉动态链接器额外搜索哪些路径。

优点:

不需要修改系统目录。
适合临时测试。
改动范围只影响当前 shell 及其子进程。
缺点:

关闭终端后可能失效。
不适合作为严肃发布方案。
设置不当可能导致加载到错误版本的库。
比如你可以这样验证:

ldd ./main
export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH
ldd ./main

前后对比 ldd 输出,会看到 libmyc.so 从 not found 变成具体路径。

八、解决方案四:配置 ld.so.conf.d 并执行 ldconfig
更系统化的方式是把库路径加入动态链接器配置。

例如你的库在:

/home/user/mylib/lib

可以创建配置文件:

sudo sh -c 'echo /home/user/mylib/lib > /etc/ld.so.conf.d/myc.conf'

然后执行:

sudo ldconfig

ldconfig 会更新 /etc/ld.so.cache,动态链接器后续可以从缓存中找到这些库。

这种方式适合系统级安装库。

优点:

配置持久有效。
不需要每次设置环境变量。
符合 Linux 系统管理习惯。
缺点:

需要 root 权限。
不适合随意测试大量临时库。
配置错误会影响系统范围。
九、解决方案五:编译时写入 rpath
还有一种方式:在生成可执行文件时,把动态库搜索路径写进可执行文件。

例如:

gcc main.c -I./include -L./lib -lmyc -Wl,-rpath=./lib -o main

这里的:

-Wl,-rpath=./lib

意思是把 -rpath=./lib 传给链接器。

这样运行时,动态链接器会根据可执行文件中记录的路径去找 libmyc.so。

可以使用 readelf -d 查看动态段信息:

readelf -d ./main | grep -E "RPATH|RUNPATH|NEEDED"

可能看到:

NEEDED Shared library: [libmyc.so]
RUNPATH Library runpath: [./lib]

这种方案适合把库和程序放在相对固定目录结构中的项目。

十、五种方案对比
方案 是否持久 是否需要 root 适用场景 风险
拷贝到系统库路径 持久 需要 系统安装 污染系统目录
系统路径建软链接 持久 需要 系统安装/版本管理 链接错误会影响系统
LD_LIBRARY_PATH 临时 不需要 学习、测试、临时运行 容易加载错库
ld.so.conf.d + ldconfig 持久 需要 正规系统级安装 配置影响全局
-Wl,-rpath 随程序 不需要 root 工程发布、相对路径固定 路径写死需谨慎
学习阶段建议:

优先 LD_LIBRARY_PATH 或 rpath;
不要随便把实验库丢进系统目录。

十一、为什么静态库没有这个问题
静态库 .a 在链接阶段已经把相关代码合并进可执行程序。

所以链接完成后:

rm libmyc.a
./main

程序仍然能运行。

动态库不同。可执行程序里没有完整库代码,只记录了对 libmyc.so 的依赖。运行时必须能找到 .so。

所以:

静态库问题主要发生在编译链接阶段;
动态库问题既可能发生在编译链接阶段,也可能发生在运行加载阶段。

十二、几个常用排查命令
1. ldd
查看动态依赖:

ldd ./main

重点看有没有:

not found

2. file
查看文件类型:

file ./main
file ./lib/libmyc.so

可以判断是不是 ELF、是不是 shared object。

3. readelf -d
查看动态段:

readelf -d ./main

重点看:

NEEDED
RPATH
RUNPATH

4. ldconfig -p
查看缓存中有哪些库:

ldconfig -p | grep myc

如果你已经配置了 ld.so.conf.d 并执行了 ldconfig,但这里查不到库,就说明配置没有生效。

十三、一次完整排查流程
遇到动态库运行失败时,可以按这个顺序排查:

1. ldd ./main,看是不是 libxxx.so => not found
2. 确认 libxxx.so 是否真实存在
3. 确认编译时 -L 指向的目录是否正确
4. 确认运行时动态链接器能否找到该目录
5. 临时设置 LD_LIBRARY_PATH 验证库本身是否可用
6. 再决定使用 rpath、ldconfig 还是系统安装方案

不要一上来就复制库到 /lib64。先定位问题发生在哪个阶段。

十四、总结
动态库最容易让人困惑的点是:

编译期找库和运行期找库不是一回事。

-L 只告诉链接器编译时去哪找库;程序运行时由动态链接器负责加载 .so,它有自己的搜索路径规则。

常见解决方式包括:

LD_LIBRARY_PATH
ldconfig
-Wl,-rpath
系统库路径
软链接
————————————————
版权声明:本文为CSDN博主「倔强的石头_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2302_78391795/article/details/163655075

上一篇 做 AI 应用最难的不是模型,而是“脏数据”:文档清洗实战指南
下一篇 兄弟们,帮我看看我的网络这是出啥问题了