1、从一段尴尬的经历说起
你一定遇到过这样的场景:
同事给了你一个编译好的程序,你兴冲冲地双击运行——弹出错误框:找不到 xxx.dll。
或者你在 Linux 下编译好了程序,拷贝到另一台机器上运行:
./myapp: error while loading shared libraries: libxxx.so.1: cannot open shared object file
你很困惑:明明编译的时候一点问题都没有,为什么换个地方就「缺这个少那个」?
答案就藏在这篇文章的主角身上——动态库和静态库。
2、库是什么
库,就是别人写好的、已经编译好的代码集合,你可以直接拿来用,不用重复造轮子。
比如你想在 C 里算一个平方根,不需要自己实现开方算法,只需要 #include <math.h>,然后链接数学库 libm。
gcc -o calc calc.c -lm
这里的 libm 就是一个库。-lm 告诉链接器:帮我把 libm 里的代码链接进来。
所以库就是可复用的编译产物。根据链接方式的不同,库分为两种:
3、静态库:编译时直接塞进去
静态库的名字通常是 libxxx.a(Linux/macOS)或者 xxx.lib(Windows)。
.a 是 archive 的缩写,它本质上就是把多个 .o 文件打了一个包:
# 先编译成 .o
gcc -c math_utils.c -o math_utils.o
gcc -c str_utils.c -o str_utils.o
# 打包成静态库
ar rcs libutils.a math_utils.o str_utils.o
使用的时候:
gcc -o myapp main.c -L. -lutils
链接器会把 libutils.a 里用到的代码拷贝到最终的可执行文件里。
静态库的特点:
- ✅ 生成的可执行文件是自包含的,拷到哪都能跑
- ✅ 没有版本兼容问题,编译时锁定了库的版本
- ❌ 可执行文件体积大(每个程序都拷贝了一份库代码)
- ❌ 库更新后,所有用了它的程序都要重新编译
打个比方:静态库就像把菜谱直接印在书里——你走到哪都能看,但书会很厚。
4、动态库:运行时按需加载
动态库的名字是 libxxx.so(Linux)、libxxx.dylib(macOS)、xxx.dll(Windows)。
.so 是 shared object 的缩写,顾名思义:多个程序共享同一份库。
创建动态库:
gcc -shared -fPIC -o libutils.so math_utils.c str_utils.c
-shared:告诉 gcc 生成动态库-fPIC:生成位置无关代码(Position Independent Code),这是共享的关键——无论库被加载到内存的什么位置,代码都能正确执行
使用时和静态库一样:
gcc -o myapp main.c -L. -lutils
但运行的时候,系统需要找到 libutils.so 在哪。默认情况下,系统只在几个标准路径下找:
/lib、/usr/lib、/usr/local/libLD_LIBRARY_PATH环境变量指定的路径
如果库不在这些路径里,你可以:
# 临时指定
export LD_LIBRARY_PATH=/path/to/libs:$LD_LIBRARY_PATH
./myapp
或者编译时把搜索路径写进去:
gcc -o myapp main.c -L. -lutils -Wl,-rpath,/path/to/libs
动态库的特点:
- ✅ 可执行文件体积小(库代码不在里面)
- ✅ 库升级不需要重新编译程序(只要接口没变)
- ✅ 多个程序共享同一份库,节省内存
- ❌ 部署时必须保证目标机器上也有这个库
- ❌ 可能出现版本不匹配的问题(
libxxx.so.1vslibxxx.so.2)
打个比方:动态库就像图书馆的参考书——大家都知道那本书在哪,需要的时候去查,但如果你搬家了(换了环境),书就找不到了。
5、一张对比表
| 静态库 | 动态库 | |
|---|---|---|
| 文件后缀 | .a / .lib |
.so / .dylib / .dll |
| 链接时机 | 编译时 | 运行时 |
| 可执行文件大小 | 大 | 小 |
| 部署难度 | 简单(自包含) | 需要确保目标环境有库 |
| 更新方式 | 重新编译 | 替换库文件即可 |
| 内存占用 | 每个进程各自一份 | 多进程共享 |
6、一个实战建议
日常工作中,这两种库我建议这样取舍:
商业软件交付:尽量静态链接,或者把动态库打包在安装目录里。你永远不想接到用户电话说「你这个软件打不开」。
Linux 系统软件:动态链接。因为你不知道用户系统上的路径,系统的包管理器会处理好依赖。
自己团队内部:动态链接。大家环境统一,动态库升级方便,也能及时发现兼容问题。
需要持续快速迭代的库:动态链接。修了 bug 不用让下游项目全部重新编译。
下一篇文章,我们来聊一个解决动态库查找问题的神器——pkg-config。它能让你从此告别手动敲 -I 和 -L 的噩梦。
每天前进一小步,就是一个新的高度!