跳到主要内容
🔍
1.1.3已发布intermediate · 约 2.5 课时 · P0

静态库与动态库

会制作和使用 .a 静态库与 .so 动态库,理解 -L/-l 与运行路径三方案

  • 来源
  • 补充
  • 纠错

标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原

完成标准(本章)

  • 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 240
  • ✏️ 已练习:小练习正确率 ≥ 60%
  • 📝 已通过测验:分数 ≥ 60
  • 🛠️ 已掌握还需完成实践任务
学习状态:未开始

1.1.3 静态库与动态库

本章来源:静态库打包与 -L/-l 链接来自《第一阶段讲义》3.6 节【来源】;动态库制作与运行路径来自《Linux IO》9-1~9-5【来源】,其中环境变量原文误写为 LD_LIBRARY(缺 PATH 后缀),已【纠错】为 LD_LIBRARY_PATH(见勘误 err-7.1-2);对比表格、通俗解释与全部练习为新编【补充】。 平台适用性:universal(静态库部分本机 MinGW 实测;.so 动态库为 Linux 专属,本机无 Linux 未执行——命令按官方文档核对,未冒充实测;Windows 动态库形态是 DLL,见迁移训练)。

① 学习目标

  1. 说明静态库(.a)与动态库(.so/.dll)的本质区别与各自优劣;
  2. 独立完成静态库全链路:add.c → add.o → ar rcs libadd.a → -L. -ladd 链接 → 运行(本机可做);
  3. 写出 Linux 动态库从 -fPIC -shared 制作到"运行时找不到库"的三方案解决;
  4. 说出 -L 与 -l 的区别,以及 libadd.a/libadd.so 与 -ladd 的命名映射;
  5. 面对"静态 vs 动态"面试题,能按体积/更新/启动速度/部署四个维度回答。

② 前置知识

  • 必修:1.1.1(编译四阶段与 gcc 常用选项——本章全程使用 gcc;链接阶段的符号解析是本章核心);
  • 建议:1.1.2(宏与头文件——把 add 声明放进头文件的规范写法在 1.2.x 展开);
  • 环境:MinGW 可完成静态库部分实测;动态库 .so 部分需 Linux。

③ 核心概念【来源】

概念说明
静态库 .aar rcs 打包的目标文件集合;链接时整体拷入可执行文件,运行时自包含
动态库 .sogcc -fPIC -shared 生成;链接时只记引用,运行时由动态链接器加载
-L 与 -l-L<目录> 指定库搜索路径;-l<名> 按约定找 lib<名>.a/.so(libadd.a → -ladd)
-fPIC位置无关代码:代码段可加载到任意地址,是动态库的编译前提
运行路径三方案LD_LIBRARY_PATH(临时)② /etc/ld.so.conf.d/*.conf + ldconfig(系统级)③ /usr/local/lib(约定路径)
静态 vs 动态体积:大/小;更新:重编/替换 .so;启动:略快/需动态解析;部署:自包含/依赖目标机库版本

④ 通俗解释【补充】

  • 静态库像把菜谱复印进每家餐馆的菜单:每家菜单变厚(体积大),但出门不用带菜谱书(运行时自包含);菜谱改版,所有餐馆都要重印菜单(更新需重编)。
  • 动态库像把公共菜谱挂在墙上:菜单只写"见墙上 3 号菜谱"(体积小),做菜时去墙上查(运行时加载);改版只换墙上的菜谱(换 .so),但哪天墙上菜谱被撕了,厨师就做不了菜(error while loading shared libraries)。
  • -L 与 -l:-L 是"去哪家书店找书"(路径),-l 是"找哪本书"(书名)。只报书名不指路(只写 -ladd 不写 -L.),在默认书店找不到就报 cannot find -ladd——这正是本章 ⑦ 的第一个错误。

⑤ 示例代码

代码示例与验证记录

  • examples/add.c库源码 add.c(int add(int,int))✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64, 2026-08-15
    编译:gcc -c add.c -o add.o -std=c11 -Wall -Wextra -Wpedantic
    适用环境:LinuxWindows(MinGW)
    展开预期输出(实测)
    (编译成功无输出;生成 add.o)

    差异说明:-Wpedantic 在 -Wall -Wextra 之上再开严格可移植性警告,本机实测零警告;add.o 供 ex2 打包进静态库

    完整源码见 /code 代码示例页

  • examples/main.c静态库全链路:add.o → ar rcs libadd.a → -L. -ladd → app✓ 已实测(gcc 15.2.0 + GNU ar 2.46 / MinGW-w64 x86_64, 2026-08-15
    编译:gcc -c add.c -o add.o -std=c11 -Wall -Wextra -Wpedantic && ar rcs libadd.a add.o && gcc main.c -L. -ladd -o app -std=c11 -Wall -Wextra -Wpedantic
    适用环境:LinuxWindows(MinGW)
    展开预期输出(实测)
    三命令编译/打包/链接均无输出(成功)
    运行 app:
    3 + 4 = 7
    

    差异说明:三命令链路本机真实执行(零警告);-ladd 按命名约定找到 libadd.a;ar t libadd.a 显示内含 add.o

    完整源码见 /code 代码示例页

  • examples/ex3-shared-steps.txtLinux 动态库命令序列(.so 制作→链接→运行路径三方案)✓ 文档核对(非执行)· GCC 官方手册与 ld.so(8) 文档核对, 2026-08-15
    编译:按文件内顺序在 Linux 终端执行
    适用环境:Linux
    展开文档核对记录
    gcc -fPIC -shared add.c -o libadd.so
    gcc main.c -L. -ladd -o app
    export LD_LIBRARY_PATH=$PWD
    ./app
    3 + 4 = 7
    

    差异说明:本机无 Linux,未执行;命令按 GCC 官方手册与 ld.so(8) 文档核对。注意:Windows 上 -shared 生成 DLL 而非 .so;环境变量必须写全 LD_LIBRARY_PATH(资料原文漏 PATH 后缀,见勘误 err-7.1-2)

    完整源码见 /code 代码示例页

⑥ 编译与运行方法

  • ex1/ex2 三处命令一致(examples.yaml、源码头注释、正文):
gcc -c add.c -o add.o -std=c11 -Wall -Wextra -Wpedantic
ar rcs libadd.a add.o
gcc main.c -L. -ladd -o app -std=c11 -Wall -Wextra -Wpedantic
  • 运行:Windows app.exe;Linux ./app。本机(MinGW)实测输出 3 + 4 = 7,零警告。
  • ex3 为 Linux 专属命令序列:本机无 Linux,未执行,命令按 GCC 官方手册与 ld.so(8) 文档核对;在 Linux 环境逐行执行即可。
  • 顺带看一眼库里装了什么:ar t libadd.a → add.o(本机实测)。

⑦ 常见错误

症状原因解决
链接报 cannot find -ladd只写了 -ladd,链接器不知道去哪找(默认路径不含当前目录)-L.gcc main.c -L. -ladd(本机实测复现)
链接报 undefined reference to 'add'-ladd 写在 main.c 前面:链接器按顺序处理,先看到库时还没有未解析的 add 符号-ladd 放到引用它的目标文件之后(本机实测复现)
运行报 error while loading shared libraries: libadd.so链接成功但运行时动态链接器找不到库三方案:LD_LIBRARY_PATH / ld.so.conf.d+ldconfig / /usr/local/lib
export LD_LIBRARY=$PWD 不生效【纠错】环境变量名少 PATH 后缀(资料原文如此)写全 export LD_LIBRARY_PATH=$PWD,见勘误 err-7.1-2
Windows 上想按 .so 的方式排查Windows 动态库是 DLL,机制不同见迁移训练;.so 相关命令只在 Linux 有效

⑧ 小练习

小练习

学习自测:提交后才显示答案与解析(前端判分,不作为正式考试)
  • ex-1-1-3-1.链接当前目录下的 libadd.a,正确的命令是?(单选)

    ◌ 未作答
  • ex-1-1-3-2.gcc 选项中 -L 的作用是?(单选)

    ◌ 未作答
  • ex-1-1-3-3.判断:Windows 上 gcc -shared 生成的动态库与 Linux 的 .so 机制一致,可以互相替换使用。(判断)

    ◌ 未作答

⑨ 章节测验

章节测验

5 题题库 · 随机抽 5 题 · 及格线 60 分 · 前端判分(学习自测)
开始测验 →

⑩ 实战任务

实践任务

制作 libadd.a 与 libadd.so 并分别链接

把 add.c 分别做成静态库 libadd.a 与动态库 libadd.so,用同一份 main.c 分别链接运行, 体验两种库在构建与运行上的差异。

输入与输出

无输入。交付物:两条完整命令记录(含实测输出)与一份"静态 vs 动态"对比小结。

功能要求

  • 静态库:gcc -c → ar rcs → gcc -L. -ladd,本机(MinGW 或 Linux)真实执行并记录输出
  • 动态库:在 Linux 环境制作 .so(gcc -fPIC -shared)并用方案一(LD_LIBRARY_PATH)运行
  • 无 Linux 环境时:用 ex3 清单逐行理解并写出三方案,书面说明每条命令的作用(验收口径注明)
  • 对比两种库的产物文件、链接时机、运行时依赖

限制条件

  • 编译必须 -std=c11 -Wall -Wextra -Wpedantic 且零警告
  • 环境变量必须写全 LD_LIBRARY_PATH(本章勘误 err-7.1-2)
  • 不要复制资料截图代码(无法还原),用本章 add.c/main.c 重敲

验收步骤(自检清单 0/3

验收标准

  • 静态库链路输出正确且零警告(验收步骤 1)
  • 动态库部分按要求二选一完成且命令正确(验收步骤 2)
  • 对比小结四个维度齐全(验收步骤 3)

常见失败原因

  • 只写 -ladd 不写 -L.:报 cannot find -ladd
  • export LD_LIBRARY 缺 PATH 后缀:不生效
  • -ladd 放在 main.c 前面:报 undefined reference to 'add'

可选扩展

  • 用 ar t libadd.a 查看库内目标文件,nm add.o 查看符号表
  • 用 ldd app 查看 Linux 程序实际解析到的动态库(1.1.1 工具链延伸)
  • 尝试 gcc -static 完全静态链接,对比产物大小

完成必要清单后才能计入"已完成实践"(学习状态自动推导,不提供一键完成)

⑪ 面试问题

面试问题

  • 静态库和动态库有什么区别?分别适用于什么场景?高频C/C++ · medium

    要点:静态库编译时整体拷入可执行文件,运行时自包含但体积大、更新需重编;动态库运行时按需加载共享,体积小、可热更新,但存在版本依赖与找不到库的问题。

    四个维度对比:①体积——静态库把代码拷进每个可执行文件,动态库多程序共享一份;②更新—— 静态库更新必须重新链接所有使用方,动态库替换 .so 即可(ABI 兼容前提下);③启动速度—— 静态链接启动略快(少一次动态解析与重定位),动态链接首次加载需解析符号;④部署——静态 自包含单文件好分发,动态需保证目标机有对应版本的库(版本地狱)。 场景:核心高频小模块、嵌入式受限环境倾向静态;系统级公共库(libc/libm)、插件机制、 需要热更新的场景用动态。嵌入式补充:交叉编译时静态链接更省事,避免目标板库版本不匹配。

    追问:
    • 追问:什么是 ABI 兼容?(改实现不改接口与布局)
    • 追问:链接时 -ladd 同时存在 libadd.a 与 libadd.so 会选哪个?(默认优先动态)
    评分要点:
    • 四维度对比齐全
    • 运行时依赖/版本问题
    • 场景选择有依据
  • 程序启动报 error while loading shared libraries: libadd.so: cannot open shared object file,怎么排查与解决?高频C/C++ · medium

    要点:先用 ldd 确认缺失的库与已解析路径;再按三方案解决:LD_LIBRARY_PATH 临时指定、/etc/ld.so.conf.d + ldconfig 系统级、安装到 /usr/local/lib 约定路径。

    区分两个阶段:链接时找不到库报 cannot find -ladd(用 -L 解决),运行时找不到才是本题症状, 说明动态链接器(ld.so)在运行时搜索路径里没找到 .so。排查:ldd ./app 列出依赖库与实际 解析路径(not found 即缺失)。解决三方案:①临时——export LD_LIBRARY_PATH=$PWD(注意 变量名写全 PATH 后缀);②系统级——把路径写入 /etc/ld.so.conf.d/xxx.conf 后 sudo ldconfig 刷新缓存;③约定路径——安装到默认搜索路径之一如 /usr/local/lib 再 ldconfig。 面试加分:提到 ldd、ldconfig -p 查看缓存、readelf -d 查看 RPATH/RUNPATH。

    追问:
    • 追问:LD_LIBRARY_PATH 对链接阶段生效吗?(不,只对运行时)
    • 追问:-rpath 是什么?(把运行搜索路径烧进 ELF)
    评分要点:
    • 区分链接时/运行时
    • ldd 排查
    • 三方案
  • gcc 链接参数 -L 和 -l 有什么区别?C/C++ · easy

    要点:-L 指定库搜索路径(目录),-l 指定库名(自动补 lib 前缀与 .a/.so 后缀);只有 -l 没有对应 -L 时链接器在默认路径找,找不到报 cannot find -lxxx。

    -L<dir> 是"去哪找",可多次出现;-l<name> 是"找哪个",按命名约定展开为 lib<name>.a 或 lib<name>.so。默认搜索路径不含当前目录,所以本机演示必须 -L.。另两个易混点:-I 是头文件 路径(编译期),与链接无关;静态库链接时 -l 的顺序有讲究——被依赖的库要放在引用它的 目标文件之后(本机实测:-ladd 放 main.c 前会报 undefined reference to 'add')。

    追问:
    • 追问:为什么默认不搜当前目录?(安全与可重现构建考虑)
    评分要点:
    • -L 路径 / -l 库名区分
    • lib 前缀后缀映射
    • -I 易混点

⑫ 延伸阅读

  • 本地手册:man ar(打包工具)、man ld(链接器)、man ldconfig(动态链接缓存)、man ld.so(运行时搜索规则);
  • GCC 官方手册 Options for Linking(-L/-l/-shared/-static,gcc.gnu.org/onlinedocs,只引名称);
  • 《程序员的自我修养——链接、装载与库》(书名,只引名称不复制内容);
  • 下一章预告:1.1.4 Makefile——把"编译 add.o → 打包 → 链接"这条链自动化。

迁移训练(migration training)

把本章技能迁移到 LinuxWindows

环节LinuxWindows(MinGW)
静态库libadd.a(ar rcs),本机同构命令libadd.a,实测通过
动态库libadd.so(-fPIC -shared),运行路径三方案生成的是 DLL(机制不同,含导出表与 __declspec(dllexport) 规则)
找不到动态库ldd 排查 + 三方案把 DLL 放到 exe 同目录或 PATH

不变的:静态库概念与命令、-L/-l 语义、链接时符号解析;要改的:动态库文件形态与运行搜索机制。

内容来源映射

内容部分资料位置标记说明
核心概念(静态库打包、-L/-l、动态库制作与运行路径)第一阶段讲义3.6 节来源正文重新组织表述;静态库链路本机实测,动态库命令按官方文档核对
动态库运行路径与 LD_LIBRARY_PATHLinux IO(1)9-1~9-5(PAGE 28 附近)纠错原文环境变量误写为 LD_LIBRARY(缺 PATH 后缀),已按勘误 err-7.1-2 纠正为 LD_LIBRARY_PATH
静态 vs 动态对比、通俗解释、练习/测验/实战任务/面试问题补充原资料无成体系对比与练习,本章新编
示例代码(add.c/main.c/ex3 命令序列)补充原资料代码为截图无法还原,本章示例全部重写;ex1/ex2 本机实测,ex3 仅文档核对

本章勘误与更新记录(【纠错】/【更新】)

  • err-7.1-2 · 技术错误 · 出处 PAGE 28

    原文:export LD_LIBRARY= $PWD(资料原文)

    正确:应为 export LD_LIBRARY_PATH=$PWD(PATH 后缀不可省略)

    原因:第一步报告 7.1 第 2 条:环境变量名少 PATH 后缀。动态链接器只认 LD_LIBRARY_PATH;写成 LD_LIBRARY 不生效,程序启动时仍报找不到 .so