库文件——什么是静态库、动态库?
在 C/C++ 编译和链接过程中,.a、.o、.so 这三种文件类型分别代表不同的中间和最终目标文件。它们在编译过程中的用途、存储方式、使用场景都不同。虽然知道他们分别对应着静态库和动态库,但是有时候他们却是一些链接时候的罪魁祸首,比如我遇到过:
- 链接时找不到 libFortranxxx. a
- 运行一个 SPEC 06 某例子时根本停不下来
- LLVM 各版本的库支持不一(尤其是 Fortran)
- 跑 benchmark 时纠结到底是用 LLVM 的库还是 GNU 的
- compiler-rt 到底是什么
- 为什么链接时有时候要加
-ldl
有时候要加-L/某路径
推荐文献
一表速查
特性 | .o (目标文件) |
.a (静态库) |
.so (共享库) |
---|---|---|---|
是否可执行 | ❌ 不能直接运行 | ❌ 不能直接运行 | ❌ 不能直接运行 |
生成方式 | gcc -c source.c -o source.o |
ar rcs libxxx.a file1.o file2.o |
gcc -shared -fPIC -o libxxx.so file1.o file2.o |
是否需要链接 | ✅ 需要 | ✅ 需要 | ✅ 需要 |
运行时依赖 | ❌ 无需 | ❌ 无需 | ✅ 需要 .so 文件 |
体积大小 | 小 | 大(包含多个 .o ) |
小(代码共享) |
代码是否拷贝到可执行文件 | ✅ 是 | ✅ 是 | ❌ 否 |
加载方式 | 直接链接到可执行文件 | 静态链接,编译时拷贝进可执行文件 | 动态链接,运行时加载 |
适用场景 | 作为编译的中间产物 | 适合底层库(如数学库) | 适合动态加载的库(如 libc.so ) |
是否支持更新 | ❌ 需要重新编译 | ❌ 需要重新编译 | ✅ 只需替换 .so |
是否支持多个程序共享 | ❌ 仅用于单个可执行文件 | ❌ 仅用于单个可执行文件 | ✅ 多个程序可共享 |
示例编译命令 | gcc -c file.c -o file.o |
ar rcs libmath.a file1.o file2.o |
gcc -shared -fPIC file1.o file2.o -o libmath.so |
示例链接命令 | gcc main.o file.o -o main |
gcc main.c -L. -lmath -o main |
gcc main.c -L. -lmath -o main && LD_LIBRARY_PATH=. ./main |
Q & A
什么库适合. a,什么库适合. so?
- 适合
.a
(静态库):- 适用于对性能要求较高的场景,因为静态库在编译时就被完全链接到可执行文件中,运行时无需额外加载。
- 适合小型、少变的库,例如数学运算库(
libm.a
)、标准 C 库的某些变种等。 - 适用于嵌入式系统,因为可执行文件不依赖外部库,避免了动态链接带来的额外开销。
- 适合不想在运行时提供额外
.so
依赖的情况下发布的软件,比如某些商业软件的封闭源代码库。
- 适合
.so
(共享库):- 适用于代码复用率高的库,如
glibc.so
、libstdc++.so
,多个程序可以共享同一份库文件。 - 适用于需要定期更新的库,如
libssl.so
,升级时只需要替换.so
文件,而不需要重新编译所有依赖它的应用。 - 适用于插件式架构,支持
dlopen()
动态加载,如libpython.so
用于 Python 解释器的动态扩展模块。
- 适用于代码复用率高的库,如
compiler-rt到底是什么?
compiler-rt
是 LLVM 提供的一个运行时库(runtime library),用于替代 GCC 的 libgcc
。它包含各种编译器支持的辅助函数,例如:
__muldi3
、__divdi3
这类整数运算支持(当 CPU 不直接支持某些运算时)。ubsan
(Undefined Behavior Sanitizer)、asan
(Address Sanitizer)等运行时检测工具的实现。builtins
(内建函数支持),如__builtin_trap
或__sync_fetch_and_add
。compiler-rt
主要用于 Clang 编译的程序,尤其是在 Clang 作为交叉编译工具链时,需要它来提供运行时支持。
为什么链接时有时候要加 -ldl
,有时候要加 -L/某路径
?
-ldl
:用于链接libdl.so
,它提供了dlopen()
、dlsym()
这些用于动态加载共享库的 API。如果代码里使用了dlopen()
但未链接-ldl
,编译可能会失败,提示undefined reference to dlopen
。-L/某路径
:指定额外的库搜索路径。例如,如果libmylib.so
在/opt/mylib/
目录下,而不是标准库路径(如/usr/lib
),就需要-L/opt/mylib -lmylib
,否则链接器找不到这个库。
能编译为 .so
的是否都可以编为 .a
,反之亦然?
.so
→.a
:通常可以直接将共享库的对象文件(.o
)打包成静态库(.a
),但需要注意某些.so
可能会依赖PIC
(Position-Independent Code),而.a
不要求PIC
。.a
→.so
:并非所有.a
都能转换成.so
,如果静态库中的代码不是PIC
编译的(即未加-fPIC
选项),则在尝试创建.so
时可能会报错。
使用不兼容的库产生的常见问题和报错有哪些?如何解决?
- 符号未定义(undefined reference to XXX):
- 可能是缺少必要的库,例如
-lm
(数学库)、-lpthread
(线程库)。 - 可能是库的路径未正确指定,需要
-L
选项手动添加路径。 - 可能是 ABI(Application Binary Interface)不兼容,例如 C++ 代码在不同编译器版本间可能导致
libstdc++.so
版本不匹配。
- 可能是缺少必要的库,例如
version 'GLIBC_XXX' not found
:- 运行时库版本过低,解决办法是安装合适的
glibc
版本,或者使用LD_LIBRARY_PATH
指定新的库路径。
- 运行时库版本过低,解决办法是安装合适的
relocation R_X86_64_32 against '.rodata' can not be used when making a shared object
:- 说明
.o
文件不是PIC
生成的,需要用-fPIC
重新编译。
- 说明
symbol lookup error: undefined symbol
:- 运行时找不到某个动态库符号,可能是
LD_LIBRARY_PATH
没有正确配置,或者需要使用-rpath
设置库路径。
- 运行时找不到某个动态库符号,可能是
如何查看 .so
或 .a
里面包含哪些符号?
.so
(共享库):nm -D libexample.so
.a
(静态库):nm libexample.a
- 还可以用
objdump -T libexample.so
来查看动态符号表。
如何强制使用某个特定的共享库?
- 使用
LD_LIBRARY_PATH
指定搜索路径 - 或者在链接时指定
-Wl,-rpath
如何让 .a
也支持动态链接(减少可执行文件体积)?
如果静态库 .a
需要动态加载,可使用 -Wl,--whole-archive
链接:
gcc main.c -Wl,--whole-archive libmylib.a -Wl,--no-whole-archive -o main
评论