在 C/C++ 编译和链接过程中,.a、.o、.so 这三种文件类型分别代表不同的中间和最终目标文件。它们在编译过程中的用途、存储方式、使用场景都不同。虽然知道他们分别对应着静态库和动态库,但是有时候他们却是一些链接时候的罪魁祸首,比如我遇到过:

一表速查

特性.o(目标文件).a(静态库).so(共享库)
是否可执行❌ 不能直接运行❌ 不能直接运行❌ 不能直接运行
生成方式gcc -c source.c -o source.oar rcs libxxx.a file1.o file2.ogcc -shared -fPIC -o libxxx.so file1.o file2.o
是否需要链接✅ 需要✅ 需要✅ 需要
运行时依赖❌ 无需❌ 无需✅ 需要 .so 文件
体积大小大(包含多个 .o小(代码共享)
代码是否拷贝到可执行文件✅ 是✅ 是❌ 否
加载方式直接链接到可执行文件==静态链接==,编译时拷贝进可执行文件动态链接,==运行时加载==
适用场景作为编译的中间产物适合底层库(如数学库)适合动态加载的库(如 libc.so
是否支持更新❌ 需要重新编译❌ 需要重新编译✅ 只需替换 .so
是否支持多个程序共享❌ 仅用于单个可执行文件❌ 仅用于单个可执行文件✅ 多个程序可共享
示例编译命令gcc -c file.c -o file.oar rcs libmath.a file1.o file2.ogcc -shared -fPIC file1.o file2.o -o libmath.so
示例链接命令gcc main.o file.o -o maingcc main.c -L. -lmath -o maingcc main.c -L. -lmath -o main && LD_LIBRARY_PATH=. ./main

Q & A

什么库适合. a,什么库适合. so?

  • 适合 .a(静态库)
    • 适用于对==性能要求较高的场景==,因为静态库在编译时就被完全链接到可执行文件中,运行时无需额外加载。
    • 适合小型、少变的库,例如数学运算库(libm.a)、标准 C 库的某些变种等。
    • 适用于==嵌入式系统==,因为可执行文件不依赖外部库,避免了动态链接带来的额外开销。
    • 适合不想在运行时提供额外 .so 依赖的情况下发布的软件,比如某些商业软件的封闭源代码库。
  • 适合 .so(共享库)
    • 适用于代码复用率高的库,如 glibc.solibstdc++.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_addcompiler-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