跳转到内容

静态链接与动态链接

上一节提到,链接阶段负责把目标文件和它依赖的库组合在一起。库本身又分为静态库和动态库两种形式,两者被链接进最终程序的时机和方式完全不同,这也是它们各自优劣的根本来源。

静态库(在 Linux 上通常是 .a 后缀的文件)本质上是一批目标文件的打包合集。链接阶段如果依赖的是静态库,链接器会把静态库中被实际用到的那部分代码,直接复制一份拼接进最终生成的可执行文件内部。

这意味着最终的可执行文件是完全自包含的——运行时不再需要这个静态库文件本身存在,因为需要的代码已经被复制进可执行文件了。代价是可执行文件体积会明显增大,而且如果同一台机器上有多个程序都静态链接了同一个库,这部分代码会在磁盘和内存中被重复存储多份,而不是共享同一份。

动态库(Linux 上通常是 .so,Shared Object 后缀的文件)在链接阶段并不会把代码复制进可执行文件,而只是在可执行文件里记录“运行时需要加载哪个动态库、需要用到库里的哪些符号(函数/变量名)“这类引用信息。真正的代码加载,发生在程序启动运行的那一刻,由操作系统的动态链接器(Dynamic Linker)负责找到对应的 .so 文件,把其中用到的代码映射进程序的内存空间。

这带来两个直接的好处:可执行文件体积明显更小,因为库代码没有被复制进去;同一份动态库文件如果被多个正在运行的程序共同依赖,操作系统可以让它们在内存中共享同一份代码实例,而不需要各自占用一份独立的内存空间。

维度 静态链接 动态链接
链接时机 编译打包时 程序启动运行时
可执行文件体积 较大(包含库代码) 较小(只包含引用信息)
依赖分发 不需要额外分发库文件 需要确保运行环境中存在对应版本的库
库更新方式 库有更新需要重新编译整个程序 替换对应的 .so 文件即可,程序无需重新编译

动态链接的库更新优势也伴随着对应的风险:如果运行环境中缺少某个动态库、或者版本和编译时使用的不匹配,程序在启动时会直接报错找不到对应的符号,这类问题统称为“依赖地狱”(Dependency Hell),是动态链接相比静态链接更容易遇到的一类问题。

查看一个可执行文件依赖了哪些动态库

Section titled “查看一个可执行文件依赖了哪些动态库”
Terminal window
ldd /usr/bin/some-program

ldd 会列出目标程序在运行时依赖的所有动态库,以及系统实际能不能找到对应的文件——如果某一行显示 => not found,说明程序缺少这个依赖,启动时大概率会报错退出,这是排查“程序装了但打不开”这类问题时常用的第一步。

这个主题是构建可靠 Linux 开发流程的重要组成部分。对它有清晰的理解,会让后续任务变得更容易,因为你能更快地判断某一步是否缺失或使用不当。

  • 在安全的测试环境中尝试这里展示的命令和配置。
  • 比较这些概念在不同发行版或工具之间的适用差异。
  • 记下一次实践中哪些步骤成功,哪些失败,这样下次遇到问题时更容易定位。
  • 将这篇文章与系列中相关内容结合起来,加深整体理解。
  • 跳过验证步骤,默认系统已经配置正确。
  • 不对命令中的路径、软件包名称或工具版本进行适配,直接复制粘贴执行。
  • 把这个主题当成孤立的技巧,而不是整个开发流程的一部分。