静态库和动态库有什么区别?
更新时间:2026-09-20。问题驱动短答页。Linux 上静态库是
.a、动态库是.so;Windows 对应.lib(静态库/导入库)与.dll。深入机制见 静态库与动态库 与 编译、链接与 ELF。
一句话回答
静态库在编译链接时把用到的代码直接复制进可执行文件,产物大但自包含、部署简单;动态库不打进程序,而在程序启动/运行时由动态加载器映射进来,多进程共享同一份库内存、升级库通常不用重编主程序,但要处理运行期找不找得到与版本兼容问题。
核心对照
| 维度 | 静态库 | 动态库 |
|---|---|---|
| Linux / Windows | .a / .lib | .so / .dll |
| 链接时机 | 链接期直接归档进可执行文件 | 只记录依赖与符号,运行时加载 |
| 可执行体积 | 大(含库代码) | 小(库在外部) |
| 内存共享 | 每个程序各一份 | 多进程共享同一份物理内存 |
| 部署 | 单文件即可跑,无外部依赖 | 必须带上匹配版本的 .so/.dll |
| 升级库 | 要重新编译链接主程序 | 替换库文件即可(ABI 兼容时) |
| 启动 | 无符号解析开销 | 启动时做动态重定位,略慢 |
| 典型 | 追求独立部署、静态 Go/Rust 二进制 | 系统库(glibc)、插件、安全补丁热更 |
怎么构建和链接
bash
# 静态库:把 .o 归档成 .a
gcc -c add.c -o add.o
ar rcs libadd.a add.o
gcc main.c -L. -ladd -o main_static # 链接期把 add 复制进去
# 动态库:-fPIC 生成位置无关代码,-shared 出 .so
gcc -fPIC -shared add.c -o libadd.so
gcc main.c -L. -ladd -o main_dyn # 仅记录对 libadd 的依赖
LD_LIBRARY_PATH=. ./main_dyn # 运行时要能找到 libadd.so
ldd ./main_dyn # 查看依赖哪些 .so-fPIC(位置无关代码)让库代码可被加载到任意地址共享,是构建 .so 的关键;-ladd 会按搜索路径找 libadd.a 或 libadd.so(默认优先动态),可用 -static 强制全静态。
运行期找不到动态库
报错 error while loading shared libraries: libadd.so: cannot open shared object file 时:
- 临时:
export LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATH; - 永久:
ldconfig+ 把路径写进/etc/ld.so.conf.d/; - 或链接时
-Wl,-rpath,'$ORIGIN/lib'把相对可执行文件的库路径写进程序。
这与编译期 undefined reference 是两阶段问题,见 undefined reference 排查。
怎么选
- 要单一二进制、免依赖部署(容器极简镜像、交叉编译、命令行工具):静态链接(Go/Rust 默认倾向)。
- 多个程序共用大库、库要独立打补丁(安全更新)、插件化:动态链接。
- 嵌入式/特殊环境:静态更省心,但注意 glibc 静态链接的 NSS/dlopen 限制。
常见坑
- 静态链接以为完全无依赖:glibc 的某些功能(域名解析 getaddrinfo、
dlopen)即使静态也可能运行期依赖 NSS 模块。 - 动态库升级破坏 ABI:改了结构体布局/函数签名但只换 .so,会出现诡异崩溃;遵循 soname/语义化版本。
- 开发机有库、目标机没有:动态程序要随包发布 .so 或做镜像;静态则无此问题。
- 混淆 Windows .lib:它可能是静态库,也可能只是给 .dll 用的导入库。
- 不装开发包:编译需要
.so软链和头文件(-dev/-devel),运行只要.so.N。 - 多份同名库版本冲突:用
ldd、LD_DEBUG=libs看实际加载了哪一份。
总结
静态库在链接期把目标代码直接归档进可执行文件,程序自包含、部署简单但体积大、库升级需重新链接;动态库以 -fPIC -shared 生成 .so/.dll,只在可执行文件里记录依赖,运行时由加载器映射并支持多进程共享与库独立升级,但必须保证目标机存在 ABI 兼容的库并配置好查找路径(LD_LIBRARY_PATH/ldconfig/rpath);追求单一二进制选静态、需要共享与热更选动态,用 ldd 检查动态依赖、undefined reference 与运行期找不到库是两个不同阶段的问题。