构建工具(三):静态库 vs 动态库——.a、.so、.dylib 到底有什么不同

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)。

.aarchive 的缩写,它本质上就是把多个 .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)。

.soshared 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/lib
  • LD_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.1 vs libxxx.so.2

打个比方:动态库就像图书馆的参考书——大家都知道那本书在哪,需要的时候去查,但如果你搬家了(换了环境),书就找不到了。

5、一张对比表

  静态库 动态库
文件后缀 .a / .lib .so / .dylib / .dll
链接时机 编译时 运行时
可执行文件大小
部署难度 简单(自包含) 需要确保目标环境有库
更新方式 重新编译 替换库文件即可
内存占用 每个进程各自一份 多进程共享

6、一个实战建议

日常工作中,这两种库我建议这样取舍:

商业软件交付:尽量静态链接,或者把动态库打包在安装目录里。你永远不想接到用户电话说「你这个软件打不开」。

Linux 系统软件:动态链接。因为你不知道用户系统上的路径,系统的包管理器会处理好依赖。

自己团队内部:动态链接。大家环境统一,动态库升级方便,也能及时发现兼容问题。

需要持续快速迭代的库:动态链接。修了 bug 不用让下游项目全部重新编译。


下一篇文章,我们来聊一个解决动态库查找问题的神器——pkg-config。它能让你从此告别手动敲 -I-L 的噩梦。

每天前进一小步,就是一个新的高度!