<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Better Mistakes</title><description>Trial.Error.Iteration.</description><link>https://bfmhno3.github.io/</link><templateTheme>Firefly</templateTheme><templateThemeVersion>6.16.7</templateThemeVersion><templateThemeUrl>https://github.com/CuteLeaf/Firefly</templateThemeUrl><lastBuildDate>2026年9月6日 15:31:30</lastBuildDate><item><title>我会用 Git，但它到底在做什么？从对象数据库到现代工作流</title><link>https://bfmhno3.github.io/posts/understanding-git/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/understanding-git/</guid><description>我用 Git 2.54.0 建了一个隔离仓库，从 blob、tree、commit、index、ref 和 reflog 拆开 add、commit、branch、merge、rebase、fetch、pull 与 push，再整理一套意图明确、可恢复的现代 Git 命令行。</description><pubDate>Sun, 06 Sep 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我会 &lt;code&gt;git add&lt;/code&gt;、&lt;code&gt;git commit&lt;/code&gt;、&lt;code&gt;git pull&lt;/code&gt; 和 &lt;code&gt;git push&lt;/code&gt;，但很长时间里，这些命令在我脑中只是一条能让代码抵达 GitHub 的传送带。AI 当然可以替我操作，而且大多数时候会操作得更快；但如果我自己分不清工作区、暂存区、引用和远端跟踪分支，就连 AI 刚刚改写了历史还是只更新了一个指针都无法判断。于是我用 Git 2.54.0 建了一个隔离仓库，从对象数据库开始，把平时敲下的命令逐层拆开。&lt;/p&gt;
&lt;p&gt;这不是一张命令速查表。我的目标是先建立模型，再从模型推导命令。Git 自己给出的冷幽默式描述是 “the stupid content tracker”，但它的底层其实相当小：一个内容寻址对象数据库，加上一组会移动的名字，再配一个准备下一次快照的 index。命令很多，核心没有那么多。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git add&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git commit&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;移动当前分支&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git push&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;merge 或 rebase&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;工作区 Working Tree&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;暂存区 Index&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;对象数据库 Objects&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/heads/main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;HEAD&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git fetch&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/remotes/origin/main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;远端 refs/heads/main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git add&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git commit&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;移动当前分支&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git push&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;merge 或 rebase&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;工作区 Working Tree&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;暂存区 Index&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;对象数据库 Objects&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/heads/main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;HEAD&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git fetch&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/remotes/origin/main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;远端 refs/heads/main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这张图是全文的地图。后面的绝大多数 Git 操作，都只是复制内容、创建不可变对象，或者让某个引用从一个 commit 移到另一个 commit。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先把几个基础概念钉牢&lt;a href=&quot;#先把几个基础概念钉牢&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我以前会说”这个文件已经在 Git 里了”，但这句话至少可能指 4 件不同的事：文件出现在工作目录里、内容进入 index、内容被某个 commit 引用，或者 commit 已经推到远端。Git 的难点有一半来自把这些状态压成了一个模糊的”保存”。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;仓库与工作区不是同一个东西&lt;a href=&quot;#仓库与工作区不是同一个东西&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Repository（仓库）&lt;/strong&gt; 主要指 &lt;code&gt;.git&lt;/code&gt; 中的对象、引用和配置。&lt;strong&gt;Working tree（工作区或工作树）&lt;/strong&gt; 是我在编辑器里看到、编译器会读取的那份文件集合。一个普通项目目录通常把两者放在一起：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket-git/          # 工作区根目录&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── README.md        # 可以直接编辑的文件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── src/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│   └── app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└── .git/            # 仓库元数据与对象数据库&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;删除工作区文件不等于立即删除历史中的 blob；删除 &lt;code&gt;.git&lt;/code&gt; 则会让这个目录失去本地 Git 历史、refs 和 index，只剩普通文件。bare repository 没有普通工作区，服务器端仓库经常采用这种形态。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;上一段中的”工作区”是 Git working tree，不是 IDE 把多个文件夹组织起来时所说的 workspace。两者可能指向同一目录，也可能完全不是一层概念。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;暂存区不是待上传清单&lt;a href=&quot;#暂存区不是待上传清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Index（索引，也叫 staging area 或暂存区）&lt;/strong&gt; 保存下一次 commit 准备采用的路径、文件模式和 blob ID。它不是一个只有文件名的勾选列表，也不是尚未 push 的 commit 队列，而是一份接近完整目录快照的二进制数据结构。&lt;/p&gt;&lt;p&gt;这解释了一个初学时很反直觉的现象：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;version 1\n&apos;&lt;/span&gt;&lt;span&gt; &amp;gt; &lt;/span&gt;&lt;span&gt;app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;version 2\n&apos;&lt;/span&gt;&lt;span&gt; &amp;gt; &lt;/span&gt;&lt;span&gt;app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;record app&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这个 commit 收进去的是 &lt;code&gt;version 1&lt;/code&gt;，因为 &lt;code&gt;git add&lt;/code&gt; 当时把那份内容写入对象数据库并让 index 指向它。之后写入工作区的 &lt;code&gt;version 2&lt;/code&gt; 仍未暂存。&lt;code&gt;git add&lt;/code&gt; 更准确的理解不是”开始跟踪这个文件”，而是”把这些路径此刻的内容放入下一张快照”。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;HEAD&lt;/code&gt;、branch 与 commit&lt;a href=&quot;#headbranch-与-commit&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Commit&lt;/strong&gt; 是不可变对象，记录根 tree、parent、身份、时间和消息。&lt;strong&gt;Branch&lt;/strong&gt; 是 &lt;code&gt;refs/heads/...&lt;/code&gt; 下会移动的引用。&lt;strong&gt;&lt;code&gt;HEAD&lt;/code&gt;&lt;/strong&gt; 表示当前检出位置，通常间接指向某个本地分支。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;HEAD -&amp;gt; refs/heads/main -&amp;gt; commit 0063127 -&amp;gt; parent cd5e714&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;执行 &lt;code&gt;git commit&lt;/code&gt; 时，Git 创建新 commit，再让当前分支指向它；旧 commit 没有被修改。执行 &lt;code&gt;git switch topic&lt;/code&gt; 时，Git 改变 &lt;code&gt;HEAD&lt;/code&gt; 所指分支，并让 index 与工作区尽量匹配目标快照。分支是名字，commit 才是历史节点。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;tracked、untracked、modified 与 staged&lt;a href=&quot;#trackeduntrackedmodified-与-staged&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这些词描述的是路径与 3 份状态之间的关系：&lt;/p&gt;







































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;状态&lt;/th&gt;&lt;th&gt;含义&lt;/th&gt;&lt;th&gt;常见下一步&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;untracked&lt;/td&gt;&lt;td&gt;工作区有这个路径，但 index 与 &lt;code&gt;HEAD&lt;/code&gt; 都没有记录它&lt;/td&gt;&lt;td&gt;&lt;code&gt;git add &amp;lt;path&amp;gt;&lt;/code&gt; 或写入 &lt;code&gt;.gitignore&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;unmodified&lt;/td&gt;&lt;td&gt;工作区、index 与 &lt;code&gt;HEAD&lt;/code&gt; 对该路径的内容一致&lt;/td&gt;&lt;td&gt;不需要动作&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;modified&lt;/td&gt;&lt;td&gt;工作区内容不同于 index&lt;/td&gt;&lt;td&gt;查看 &lt;code&gt;git diff&lt;/code&gt;，再暂存或恢复&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;staged&lt;/td&gt;&lt;td&gt;index 内容不同于 &lt;code&gt;HEAD&lt;/code&gt;&lt;/td&gt;&lt;td&gt;查看 &lt;code&gt;git diff --staged&lt;/code&gt;，再 commit 或取消暂存&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;deleted&lt;/td&gt;&lt;td&gt;某次比较的一侧已有路径，另一侧没有&lt;/td&gt;&lt;td&gt;暂存删除、恢复文件或提交删除&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;conflicted&lt;/td&gt;&lt;td&gt;index 中存在尚未解决的高 stage 条目&lt;/td&gt;&lt;td&gt;解决内容，&lt;code&gt;git add&lt;/code&gt; 后继续操作&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;一个文件可以同时 modified 与 staged。前面的 &lt;code&gt;MM src/app.txt&lt;/code&gt; 就是：index 已准备一个版本，工作区又在它上面继续修改。&lt;code&gt;git status&lt;/code&gt; 给状态摘要，&lt;code&gt;git diff&lt;/code&gt; 展开具体内容；前者像仪表盘，后者才是示波器。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;本地分支、远端跟踪分支与 upstream&lt;a href=&quot;#本地分支远端跟踪分支与-upstream&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这 3 个名字也经常被混成”远端分支”：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;main&lt;/code&gt; 是本地分支，我可以离线提交并移动它；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;origin/main&lt;/code&gt; 是本地保存的 remote-tracking ref，表示上次 fetch 后我看到的远端 &lt;code&gt;main&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;服务器上的 &lt;code&gt;refs/heads/main&lt;/code&gt; 才是远端此刻真正的分支。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Upstream&lt;/strong&gt; 是给本地分支配置的默认比较、pull 和 push 对象。例如 &lt;code&gt;git push -u origin topic&lt;/code&gt; 会在发布分支的同时设置 upstream。upstream 不是父 commit，也不意味着本地分支会自动与服务器同步。&lt;/p&gt;&lt;p&gt;最后把最常见的状态流压成 4 步：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;远端仓库本地仓库Index工作区远端仓库本地仓库Index工作区我编辑文件git add：写入 blob 并更新 indexgit commit：创建快照并移动本地 branchgit push：发送对象并请求移动远端 ref我
&lt;/div&gt;&lt;div&gt;远端仓库本地仓库Index工作区远端仓库本地仓库Index工作区我编辑文件git add：写入 blob 并更新 indexgit commit：创建快照并移动本地 branchgit push：发送对象并请求移动远端 ref我
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这 4 步可以分开检查，也应该分开理解。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;先说结论：Git 保存的是快照，不是补丁&lt;a href=&quot;#先说结论git-保存的是快照不是补丁&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;很多介绍会把 Git 描述成一个逐行保存差异的工具。这对阅读 &lt;code&gt;git diff&lt;/code&gt; 很直观，但不适合作为底层模型。&lt;/p&gt;&lt;p&gt;每次 commit 指向一棵完整目录树。没有变化的文件会继续引用原来的 blob，所以 Git 不需要重复创建内容相同的对象；发生变化的文件得到新的 blob。Git 在对象语义上保存快照，在 packfile 的物理存储里才会为了压缩寻找 delta。换句话说：&lt;strong&gt;快照是数据模型，差异是比较结果和存储优化。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;我把 Git 想成一座仓库：对象是贴着内容指纹的密封箱，commit 是一张指向整批箱子的装箱单，branch 只是贴在某张装箱单上的可移动书签。这个比喻到这里就够了，接下来直接看字节。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;从零建一个可以解剖的仓库&lt;a href=&quot;#从零建一个可以解剖的仓库&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我不会拿正在工作的项目做实验。先创建临时目录，并显式指定初始分支名：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;repo&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;mktemp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$repo&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--initial-branch=main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user.name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Ada&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user.email&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ada@example.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我的实验版本是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git version 2.54.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;git init&lt;/code&gt; 没有创建任何 commit。它只创建 &lt;code&gt;.git&lt;/code&gt; 管理目录。下面是采用默认 &lt;code&gt;files&lt;/code&gt; 引用后端的仓库在后续使用中会出现的关键路径；&lt;code&gt;index&lt;/code&gt;、&lt;code&gt;refs/remotes&lt;/code&gt; 和 &lt;code&gt;logs&lt;/code&gt; 并不一定在刚初始化时就存在：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.git/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── config&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── index              # 第一次 add 后出现&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── objects/           # 对象数据库&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── refs/heads/        # 本地分支引用&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── refs/remotes/      # fetch 后的远端跟踪引用&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└── logs/              # 启用 reflog 的引用发生更新后出现&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;现代脚本应使用 &lt;code&gt;git init --initial-branch=main&lt;/code&gt;，而不是依赖某个 Git 版本或用户配置碰巧采用什么默认分支名。长期偏好则可以配置：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init.defaultBranch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;code&gt;--global&lt;/code&gt; 修改当前用户的 Git 配置，不应该不加判断地写进项目安装脚本。项目级策略尽量放在仓库配置、服务端规则或文档中，个人偏好留给个人。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第一个 blob：内容如何变成 ID&lt;a href=&quot;#第一个-blob内容如何变成-id&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;写一个 13 字节文件（结尾换行也算 1 字节）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;# pocket-git\n&apos;&lt;/span&gt;&lt;span&gt; &amp;gt; &lt;/span&gt;&lt;span&gt;README.md&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hash-object&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;README.md&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我得到：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;5d87241c4953134b72c3ef069243777aa25121d9&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这个 40 位 object ID 不是只对文件内容计算 SHA-1。Git 实际哈希的是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;blob 13\0# pocket-git\n&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;也就是对象类型、一个空格、十进制长度、NUL 字节，再接原始内容：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; hashlib&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;content &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;&quot;# pocket-git&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;raw &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;blob &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(content)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;\0&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; content&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;&lt;span&gt;(hashlib.&lt;/span&gt;&lt;span&gt;sha1&lt;/span&gt;&lt;span&gt;(raw).&lt;/span&gt;&lt;span&gt;hexdigest&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;输出仍然是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;5d87241c4953134b72c3ef069243777aa25121d9&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;git hash-object README.md&lt;/code&gt; 默认只计算，不写入数据库；加 &lt;code&gt;-w&lt;/code&gt; 才会写。平时不需要直接执行它，因为 &lt;code&gt;git add&lt;/code&gt; 会完成这一步。&lt;/p&gt;&lt;p&gt;Git 2.54.0 新仓库仍默认使用 SHA-1，也支持 &lt;code&gt;git init --object-format=sha256&lt;/code&gt;。不过 SHA-1 与 SHA-256 仓库目前不能直接互操作，所以不要为了看起来更现代就擅自切换团队仓库的对象格式。这里的 object ID 首先承担内容寻址和完整性校验；它不是文件加密，也不等于 commit 签名。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;git add&lt;/code&gt; 不是”添加文件”，而是更新下一张快照&lt;a href=&quot;#git-add-不是添加文件而是更新下一张快照&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;再写一个文件，然后暂存两者：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;version 1\n&apos;&lt;/span&gt;&lt;span&gt; &amp;gt; &lt;/span&gt;&lt;span&gt;src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;README.md&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls-files&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--stage&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我看到：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;100644 5d87241c4953134b72c3ef069243777aa25121d9 0 README.md&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;100644 83baae61804e65cc73a7201a7252750c76066a30 0 src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这就是 index 的可读投影。每行记录文件模式、blob ID、stage 编号和路径。&lt;code&gt;100644&lt;/code&gt; 是普通非可执行文件；最后的 &lt;code&gt;0&lt;/code&gt; 表示正常条目。普通内容冲突中，同一路径最多还可能出现 stage 1、2、3，分别保存 merge base、ours 和 theirs；删除等冲突类型可能缺少其中某项，而且 rebase 里的 ours 与 theirs 要从重放操作而非原分支名理解。&lt;/p&gt;&lt;p&gt;所以 &lt;code&gt;git add&lt;/code&gt; 这个名字稍微有点误导。它不仅”添加新文件”，还会把已修改文件的&lt;strong&gt;当前内容&lt;/strong&gt;写成 blob，并更新 index 中对应路径。之后继续编辑同一个文件，不会自动改变已经暂存的内容。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;从 index 写出 tree，再创建 commit&lt;a href=&quot;#从-index-写出-tree再创建-commit&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;index 还不是 tree 对象，但它是 tree 的施工图。执行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;write-tree&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我的根 tree ID 是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;208dc736f90acf95e1f43a0a0ae122b906953eef&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;查看它：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cat-file&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;208dc736f90acf95e1f43a0a0ae122b906953eef&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;会看到根目录中的 blob 和 &lt;code&gt;src&lt;/code&gt; 子 tree。tree 保存名字、模式和下一级对象 ID；blob 只保存内容，不知道自己叫 &lt;code&gt;README.md&lt;/code&gt; 还是 &lt;code&gt;copy.md&lt;/code&gt;。两个路径内容完全相同，可以指向同一个 blob。&lt;/p&gt;&lt;p&gt;现在提交。为了让下面的 commit ID 可以复算，我除了使用前面配置的身份，还临时固定 author date 和 committer date：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GIT_AUTHOR_DATE&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&apos;2026-09-06T12:00:00+08:00&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GIT_COMMITTER_DATE&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$GIT_AUTHOR_DATE&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;feat: build the first snapshot&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我得到：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd5e7140591d1099e8a990fe16f839a4b91be271&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;直接查看对象：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cat-file&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;tree 208dc736f90acf95e1f43a0a0ae122b906953eef&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;author Ada &amp;lt;ada@example.com&amp;gt; 1788667200 +0800&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;committer Ada &amp;lt;ada@example.com&amp;gt; 1788667200 +0800&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;feat: build the first snapshot&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;第一个 commit 没有 &lt;code&gt;parent&lt;/code&gt;。后续普通 commit 有 1 个 parent，merge commit 通常有 2 个或更多 parent。commit 本身不装文件内容，只记录：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;根 tree；&lt;/li&gt;
&lt;li&gt;parent commit；&lt;/li&gt;
&lt;li&gt;author 与 author date；&lt;/li&gt;
&lt;li&gt;committer 与 committer date；&lt;/li&gt;
&lt;li&gt;commit message；&lt;/li&gt;
&lt;li&gt;可选签名等 header。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;因此修改 commit message、作者、时间、parent 或 tree 中任意一个字节，都会得到新的 commit ID；它之后的所有子 commit 也必须改写，因为每个子 commit 都记录 parent ID。这就是 &lt;code&gt;rebase&lt;/code&gt;、&lt;code&gt;commit --amend&lt;/code&gt; 和历史过滤为什么会成串地产生新 ID。&lt;/p&gt;&lt;p&gt;把这些对象连起来以后，第一张快照实际是一张由 ID 串起的 DAG：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;README.md / 100644&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;src / 040000&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;app.txt / 100644&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;commit cd5e714&lt;br /&gt;&lt;br /&gt;作者、时间、消息&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;root tree 208dc73&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;blob 5d87241&lt;br /&gt;&lt;br /&gt;# pocket-git&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;tree src&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;blob 83baae6&lt;br /&gt;&lt;br /&gt;version 1&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;README.md / 100644&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;src / 040000&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;app.txt / 100644&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;commit cd5e714&lt;br /&gt;&lt;br /&gt;作者、时间、消息&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;root tree 208dc73&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;blob 5d87241&lt;br /&gt;&lt;br /&gt;# pocket-git&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;tree src&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;blob 83baae6&lt;br /&gt;&lt;br /&gt;version 1&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;路径名活在 tree 的边上，文件内容活在 blob 中，commit 只从根 tree 进入整张图。这也是相同内容能够复用同一个 blob 的原因。&lt;/p&gt;&lt;p&gt;一次普通 &lt;code&gt;git commit&lt;/code&gt; 在概念上做了 4 件事：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;从 index 写出 tree 对象；&lt;/li&gt;
&lt;li&gt;创建指向该 tree 和当前 parent 的 commit 对象；&lt;/li&gt;
&lt;li&gt;把当前 branch ref 移到新 commit；&lt;/li&gt;
&lt;li&gt;在已启用的 &lt;code&gt;HEAD&lt;/code&gt; 和当前本地分支 reflog 中记录这次移动。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;它&lt;strong&gt;不会&lt;/strong&gt;自动提交工作区里尚未暂存的修改。&lt;code&gt;git commit -a&lt;/code&gt; 只会自动暂存已跟踪文件的修改和删除，仍不会包含未跟踪文件。我更偏好显式 &lt;code&gt;git add -p&lt;/code&gt;，因为省掉一次检查通常也顺便省掉了思考（这个优化不太划算）。&lt;/p&gt;&lt;p&gt;我实际做了一次：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;version 2\n&apos;&lt;/span&gt;&lt;span&gt; &amp;gt; &lt;/span&gt;&lt;span&gt;src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;version 3\n&apos;&lt;/span&gt;&lt;span&gt; &amp;gt; &lt;/span&gt;&lt;span&gt;src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--short&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;输出是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MM src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;左边的 &lt;code&gt;M&lt;/code&gt; 表示 index 相对 &lt;code&gt;HEAD&lt;/code&gt; 有修改，右边的 &lt;code&gt;M&lt;/code&gt; 表示工作区相对 index 又有修改。同一个路径此时同时存在 3 个版本：&lt;/p&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;位置&lt;/th&gt;&lt;th&gt;内容&lt;/th&gt;&lt;th&gt;查看方式&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;HEAD&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;version 1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git show HEAD:src/app.txt&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;index&lt;/td&gt;&lt;td&gt;&lt;code&gt;version 2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git show :src/app.txt&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;工作区&lt;/td&gt;&lt;td&gt;&lt;code&gt;version 3&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;cat src/app.txt&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git diff --staged&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git diff&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;HEAD&lt;br /&gt;&lt;br /&gt;version 1&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;index&lt;br /&gt;&lt;br /&gt;version 2&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;工作区&lt;br /&gt;&lt;br /&gt;version 3&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git diff --staged&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git diff&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;HEAD&lt;br /&gt;&lt;br /&gt;version 1&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;index&lt;br /&gt;&lt;br /&gt;version 2&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;工作区&lt;br /&gt;&lt;br /&gt;version 3&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这里的箭头表示比较方向，不表示内容自动流动。它把 &lt;code&gt;MM&lt;/code&gt; 的两个 &lt;code&gt;M&lt;/code&gt; 直接摊开了：左列比较 &lt;code&gt;HEAD&lt;/code&gt; 与 index，右列比较 index 与工作区。&lt;/p&gt;&lt;p&gt;对应的两次比较也完全不同：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;# 工作区 vs index&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--staged&lt;/span&gt;&lt;span&gt;         &lt;/span&gt;&lt;span&gt;# index vs HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这是我认为最值得形成肌肉记忆的提交循环：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--short&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--branch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--patch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--staged&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;git add --patch&lt;/code&gt;（缩写 &lt;code&gt;git add -p&lt;/code&gt;）按 hunk 选择内容，比不看差异就 &lt;code&gt;git add .&lt;/code&gt; 更接近”设计一个 commit”。如果同一段修改同时修 bug、改命名和格式化，我会先把它们拆成能独立解释的快照。commit 的边界是给未来的我、同事、&lt;code&gt;git bisect&lt;/code&gt; 和 AI 阅读的 API。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;code&gt;git add .&lt;/code&gt; 本身没有错，但它表达的是”暂存当前目录以下所有变化”。在运行前后都看 &lt;code&gt;git status&lt;/code&gt; 和 &lt;code&gt;git diff --staged&lt;/code&gt;，不要把它当作关闭红点的按钮。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;git diff&lt;/code&gt; 到底在比较什么&lt;a href=&quot;#git-diff-到底在比较什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;diff&lt;/code&gt; 是我每天最应该多敲几次的 Git 命令。它不修改状态，只把两个端点之间的变化编码成 patch。真正的第一个问题不是”改了什么”，而是”拿哪两份状态比较”。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;先选对两端&lt;a href=&quot;#先选对两端&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;














































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;命令&lt;/th&gt;&lt;th&gt;旧端&lt;/th&gt;&lt;th&gt;新端&lt;/th&gt;&lt;th&gt;我通常用它回答&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git diff&lt;/code&gt;&lt;/td&gt;&lt;td&gt;index&lt;/td&gt;&lt;td&gt;工作区&lt;/td&gt;&lt;td&gt;我改了但还没暂存什么？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git diff --staged&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;HEAD&lt;/code&gt;&lt;/td&gt;&lt;td&gt;index&lt;/td&gt;&lt;td&gt;下一次 commit 会包含什么？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git diff HEAD&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;HEAD&lt;/code&gt;&lt;/td&gt;&lt;td&gt;工作区&lt;/td&gt;&lt;td&gt;当前全部已跟踪修改是什么？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git diff &amp;lt;A&amp;gt; &amp;lt;B&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;commit A&lt;/td&gt;&lt;td&gt;commit B&lt;/td&gt;&lt;td&gt;从 A 变成 B 要应用什么 patch？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git diff &amp;lt;A&amp;gt;...&amp;lt;B&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;A 与 B 的 merge base&lt;/td&gt;&lt;td&gt;commit B&lt;/td&gt;&lt;td&gt;B 分支从分叉点引入了什么？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git show &amp;lt;commit&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;普通 commit 的 parent&lt;/td&gt;&lt;td&gt;该 commit&lt;/td&gt;&lt;td&gt;这个普通 commit 引入了什么？&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;方向很重要。&lt;code&gt;git diff A B&lt;/code&gt; 描述的是把 A 变成 B：&lt;code&gt;-&lt;/code&gt; 行来自 A，&lt;code&gt;+&lt;/code&gt; 行来自 B。交换参数，patch 的正负也会反过来。&lt;/p&gt;&lt;p&gt;&lt;code&gt;git diff A..B&lt;/code&gt; 对 &lt;code&gt;diff&lt;/code&gt; 来说与 &lt;code&gt;git diff A B&lt;/code&gt; 基本等价，只比较两个端点；这里的 &lt;code&gt;..&lt;/code&gt; 不具有 &lt;code&gt;git log A..B&lt;/code&gt; 那种”可达 commit 集合之差”语义。三点 &lt;code&gt;A...B&lt;/code&gt; 才会先求 merge base，再把 merge base 与 B 比较。两个点和三个点长得只差一个点，含义差了一棵分叉历史（非常 Git）。&lt;/p&gt;&lt;p&gt;只看某些路径时，我会用 &lt;code&gt;--&lt;/code&gt; 明确结束 revision 参数：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HEAD&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main...topic&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这也能避免一个路径名恰好像 branch 名时产生歧义。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;逐行拆一份 unified diff&lt;a href=&quot;#逐行拆一份-unified-diff&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在前面的实验里，index 保存 &lt;code&gt;version 2&lt;/code&gt;，工作区保存 &lt;code&gt;version 3&lt;/code&gt;。运行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;实际输出是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;diff --git a/src/app.txt b/src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;index 1f7a7a4..7170a52 100644&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;--- a/src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;+++ b/src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@@ -1 +1 @@&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-version 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;+version 3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;从上往下读。&lt;/p&gt;&lt;section&gt;&lt;h4&gt;&lt;code&gt;diff --git&lt;/code&gt;：一个文件级变化开始了&lt;a href=&quot;#diff---git一个文件级变化开始了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;diff --git a/src/app.txt b/src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这一行标记新的 file patch。&lt;code&gt;a/&lt;/code&gt; 表示变化前一侧，&lt;code&gt;b/&lt;/code&gt; 表示变化后一侧；它们通常不是仓库里真实存在的两个目录。重命名或复制时，两侧路径会不同。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;&lt;code&gt;index&lt;/code&gt;：前后 blob 与文件模式&lt;a href=&quot;#index前后-blob-与文件模式&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;index 1f7a7a4..7170a52 100644&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1f7a7a4&lt;/code&gt; 是旧端内容的 blob ID 缩写，对应 &lt;code&gt;version 2&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;7170a52&lt;/code&gt; 是新端内容计算出的 blob ID 缩写，对应 &lt;code&gt;version 3&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;100644&lt;/code&gt; 表示普通非可执行文件。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;工作区版本即使还没被 &lt;code&gt;git add&lt;/code&gt; 写成 loose object，Git 仍可以根据内容算出它应有的 object ID。文件模式发生变化时，输出通常改为独立的 &lt;code&gt;old mode&lt;/code&gt; 和 &lt;code&gt;new mode&lt;/code&gt; 行。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;&lt;code&gt;---&lt;/code&gt; 与 &lt;code&gt;+++&lt;/code&gt;：旧文件和新文件标签&lt;a href=&quot;#----与-旧文件和新文件标签&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;--- a/src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;+++ b/src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;---&lt;/code&gt; 永远标旧端，&lt;code&gt;+++&lt;/code&gt; 永远标新端。这里的 3 个减号和 3 个加号是 header 语法，不表示删除了 3 行或增加了 3 行。新建文件的旧端、删除文件的新端会在这里显示 &lt;code&gt;/dev/null&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;&lt;code&gt;@@&lt;/code&gt;：hunk 在两边的坐标&lt;a href=&quot;#hunk-在两边的坐标&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@@ -1 +1 @@&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;一个 patch 可以包含多个相隔较远的修改块，每一块叫 hunk。一般格式是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@@ -&amp;lt;旧起始行&amp;gt;,&amp;lt;旧行数&amp;gt; +&amp;lt;新起始行&amp;gt;,&amp;lt;新行数&amp;gt; @@ &amp;lt;可选上下文&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-1&lt;/code&gt; 表示旧文件从第 1 行开始、共 1 行；&lt;code&gt;+1&lt;/code&gt; 对新文件含义相同。行数为 1 时逗号和行数会省略，所以这里不是”缺了一个数字”。例如：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@@ -10,4 +10,6 @@ function parseConfig()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;表示旧端从第 10 行取 4 行，新端从第 10 行取 6 行。末尾的函数名或章节名只是帮助定位的 hunk context，不参与 patch 内容。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;hunk 正文：看每行第一个字符&lt;a href=&quot;#hunk-正文看每行第一个字符&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;version 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;version 3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;空格开头：context line，两边都存在，内容没变；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-&lt;/code&gt; 开头：只在旧端存在，要删除；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+&lt;/code&gt; 开头：只在新端存在，要加入。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这几个前缀是 patch 数据，不是装饰颜色。终端常把删除染红、增加染绿，但把颜色关掉或把 patch 发给程序后，真正可靠的仍是行首字符。&lt;/p&gt;&lt;p&gt;若文件最后一行缺少换行，Git 还会输出：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\ No newline at end of file&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这不是新增的文件内容，而是说明紧邻的那一侧末行没有 line feed。文本文件看起来完全一样，末尾字节却不同，Git 当然会认真记账（它对字节的原则性比我强得多）。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;新建、删除、改权限和重命名怎么看&lt;a href=&quot;#新建删除改权限和重命名怎么看&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;普通内容修改之外，extended headers 会描述文件级元数据：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;new file mode 100644&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;deleted file mode 100644&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;old mode 100644&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;new mode 100755&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rename from old-name.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rename to new-name.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;similarity index 98%&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;100644 -&amp;gt; 100755&lt;/code&gt; 通常表示 executable bit 发生变化，不代表文件正文改变。重命名则需要一个重要修正：Git commit 并不存储”rename old to new”操作。Git 保存前后 tree，diff 再把一个删除和一个新增按内容相似度推断为 rename。&lt;code&gt;similarity index 98%&lt;/code&gt; 表示 Git 认为 98% 的行没有变化；调整 rename detection 阈值可能得到不同展示，但 commit 对象没有改变。&lt;/p&gt;&lt;p&gt;想明确启用并设置阈值，可以：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--find-renames=50%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;A&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;B&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;二进制文件默认通常只显示 &lt;code&gt;Binary files ... differ&lt;/code&gt;，不会假装把任意字节流解释成文本行。项目可以通过 &lt;code&gt;.gitattributes&lt;/code&gt; 配置 textconv 或专用 diff driver，但那改变的是展示，不是对象内容。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;适合不同阅读阶段的 diff 视图&lt;a href=&quot;#适合不同阅读阶段的-diff-视图&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;完整 patch 不是每次都最合适。我通常从概览逐层放大：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--stat&lt;/span&gt;&lt;span&gt;                 &lt;/span&gt;&lt;span&gt;# 每个文件的增删规模&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name-status&lt;/span&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;# 路径及 M/A/D/R 等状态&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span&gt;# 完整未暂存 patch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--staged&lt;/span&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;# 完整待提交 patch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--word-diff&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;# 观察行内词级变化&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--check&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;# 检查空白错误与冲突标记&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--stat&lt;/code&gt; 适合先判断变化范围，不能代替阅读 patch；&lt;code&gt;--word-diff&lt;/code&gt; 对长段文字和一行内改多个 token 很有用，但它不是常规 unified patch；&lt;code&gt;--check&lt;/code&gt; 适合提交前发现 trailing whitespace 等问题。&lt;/p&gt;&lt;p&gt;只想让脚本判断有无差异时：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--quiet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--exit-code&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;退出码 &lt;code&gt;0&lt;/code&gt; 表示没有差异，&lt;code&gt;1&lt;/code&gt; 表示存在差异，其他非零值通常表示真正的错误。这里不能用”命令没有打印东西，所以相同”来判断，因为 &lt;code&gt;--quiet&lt;/code&gt; 本来就不打印。&lt;/p&gt;&lt;p&gt;Git 也可以离开仓库比较两个普通路径：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--no-index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;old.txt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这时它很像系统 &lt;code&gt;diff -u&lt;/code&gt;，仍使用 Git 熟悉的 patch 展示和退出码。至于 &lt;code&gt;--patience&lt;/code&gt;、&lt;code&gt;--histogram&lt;/code&gt; 等 diff algorithm，它们会改变 hunk 如何对齐，适合默认 Myers 算法给出难读结果时尝试；它们不会改变两端真实内容。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Important&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;审查 commit 前看 &lt;code&gt;git diff --staged&lt;/code&gt;，push 前看 branch 间的 diff。&lt;code&gt;git status&lt;/code&gt; 只能告诉我”哪个文件处于什么状态”，不能告诉我删掉的条件判断是不是刚好把权限检查也带走了。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;branch 和 HEAD 原来如此轻&lt;a href=&quot;#branch-和-head-原来如此轻&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;第一次提交后，我直接看两个文件：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.git/HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.git/refs/heads/main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;输出分别是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ref: refs/heads/main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd5e7140591d1099e8a990fe16f839a4b91be271&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;main&lt;/code&gt; 只是一个指向 commit 的 ref，&lt;code&gt;HEAD&lt;/code&gt; 通常又是一个指向当前 branch ref 的符号引用。创建分支几乎不复制数据：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;branch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;experiment&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cd5e714&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这只是新建 &lt;code&gt;refs/heads/experiment&lt;/code&gt;，写入同一个 commit ID。无论仓库有 10 KB 还是 100 GB，创建普通分支都不是复制完整项目。&lt;/p&gt;&lt;p&gt;第二次提交后，我得到：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;mainexperimentcd5e7140063127HEAD
&lt;/div&gt;&lt;div&gt;mainexperimentcd5e7140063127HEAD
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;此时 &lt;code&gt;main&lt;/code&gt; 指向 &lt;code&gt;0063127&lt;/code&gt;，&lt;code&gt;experiment&lt;/code&gt; 仍指向 &lt;code&gt;cd5e714&lt;/code&gt;。所谓”在某个分支上提交”，就是 Git 创建新 commit 后移动 &lt;code&gt;HEAD&lt;/code&gt; 间接指向的那个分支。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;detached HEAD 是什么&lt;a href=&quot;#detached-head-是什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;如果执行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;switch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--detach&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cd5e714&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;HEAD&lt;/code&gt; 会直接保存 commit ID，而不再指向 &lt;code&gt;refs/heads/...&lt;/code&gt;。这适合检查历史或做可丢弃实验。此时提交仍然会创建合法 commit，只是没有 branch 名自动跟着它移动。切走后，那些 commit 可能变得不可达，但通常还能暂时从 reflog 找回。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/heads/main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;0063127&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;cd5e714&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;普通 HEAD&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;detached HEAD&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/heads/main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;0063127&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;cd5e714&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;普通 HEAD&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;detached HEAD&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;普通状态下，&lt;code&gt;HEAD -&amp;gt; branch -&amp;gt; commit&lt;/code&gt;；detached 状态则跳过 branch，变成 &lt;code&gt;HEAD -&amp;gt; commit&lt;/code&gt;。少掉的正是那个会自动移动、也方便以后寻找的名字。&lt;/p&gt;&lt;p&gt;如果实验值得保留，在切走前给它一个名字：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;switch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rescued-experiment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么现在更推荐 &lt;code&gt;switch&lt;/code&gt; 和 &lt;code&gt;restore&lt;/code&gt;&lt;a href=&quot;#为什么现在更推荐-switch-和-restore&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;传统 &lt;code&gt;git checkout&lt;/code&gt; 同时承担两类工作：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;切换分支或进入 detached HEAD；&lt;/li&gt;
&lt;li&gt;用 index 或某个 commit 覆盖路径。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;同一个命令既移动 &lt;code&gt;HEAD&lt;/code&gt;，又可能覆盖文件，意图不够显眼。Git 2.23 在 2019 年加入 &lt;code&gt;git switch&lt;/code&gt; 和 &lt;code&gt;git restore&lt;/code&gt;，把两组职责拆开。&lt;strong&gt;&lt;code&gt;checkout&lt;/code&gt; 没有被废弃，也不是错误命令&lt;/strong&gt;；现代交互式工作流更推荐语义专一的命令，因为代码审查和终端历史更容易读懂。&lt;/p&gt;&lt;p&gt;常见映射如下：&lt;/p&gt;












































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;意图&lt;/th&gt;&lt;th&gt;现代命令&lt;/th&gt;&lt;th&gt;传统写法&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;切换已有分支&lt;/td&gt;&lt;td&gt;&lt;code&gt;git switch topic&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git checkout topic&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;创建并切换分支&lt;/td&gt;&lt;td&gt;&lt;code&gt;git switch -c topic&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git checkout -b topic&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;切回上一个分支&lt;/td&gt;&lt;td&gt;&lt;code&gt;git switch -&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git checkout -&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;临时查看 commit&lt;/td&gt;&lt;td&gt;&lt;code&gt;git switch --detach &amp;lt;commit&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git checkout &amp;lt;commit&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;丢弃工作区路径修改&lt;/td&gt;&lt;td&gt;&lt;code&gt;git restore &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git checkout -- &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;取消暂存&lt;/td&gt;&lt;td&gt;&lt;code&gt;git restore --staged &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git reset HEAD &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;从某 commit 取回路径&lt;/td&gt;&lt;td&gt;&lt;code&gt;git restore --source=&amp;lt;commit&amp;gt; &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git checkout &amp;lt;commit&amp;gt; -- &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;我在前面的 3 版本实验里运行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;restore&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;restore&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--staged&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/app.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;第一条把工作区从 &lt;code&gt;version 3&lt;/code&gt; 恢复到 index 的 &lt;code&gt;version 2&lt;/code&gt;；第二条把 index 从 &lt;code&gt;version 2&lt;/code&gt; 恢复到 &lt;code&gt;HEAD&lt;/code&gt; 的 &lt;code&gt;version 1&lt;/code&gt;，但工作区仍保留 &lt;code&gt;version 2&lt;/code&gt;。这里没有魔法，只是明确指定复制的来源和目的地。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git restore --staged&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git restore&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;HEAD&lt;br /&gt;&lt;br /&gt;version 1&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;index&lt;br /&gt;&lt;br /&gt;version 2&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;工作区&lt;br /&gt;&lt;br /&gt;version 3&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git restore --staged&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;git restore&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;HEAD&lt;br /&gt;&lt;br /&gt;version 1&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;index&lt;br /&gt;&lt;br /&gt;version 2&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;工作区&lt;br /&gt;&lt;br /&gt;version 3&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;restore&lt;/code&gt; 的危险程度取决于箭头落在哪里：落到工作区就可能覆盖未保存内容，落到 index 则只是重新安排下一次快照。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Caution&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;code&gt;git restore &amp;lt;path&amp;gt;&lt;/code&gt; 会覆盖尚未暂存的工作区修改。先运行 &lt;code&gt;git diff -- &amp;lt;path&amp;gt;&lt;/code&gt;。如果内容重要但还不适合 commit，可以先建临时分支提交，通常比把唯一副本塞进 stash 更容易找回。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;merge、rebase 和 cherry-pick 都在改什么&lt;a href=&quot;#mergerebase-和-cherry-pick-都在改什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;假设历史分叉成：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;maintopicOABCD
&lt;/div&gt;&lt;div&gt;maintopicOABCD
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;section&gt;&lt;h3&gt;Fast-forward：历史没分叉，只移动指针&lt;a href=&quot;#fast-forward历史没分叉只移动指针&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;先看最简单的情况。&lt;code&gt;main&lt;/code&gt; 停在 &lt;code&gt;O&lt;/code&gt;，&lt;code&gt;topic&lt;/code&gt; 从 &lt;code&gt;O&lt;/code&gt; 继续提交了 &lt;code&gt;C&lt;/code&gt;、&lt;code&gt;D&lt;/code&gt;，而 &lt;code&gt;main&lt;/code&gt; 自己没有新 commit：&lt;/p&gt;&lt;p&gt;合并前：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;maintopicOCD
&lt;/div&gt;&lt;div&gt;maintopicOCD
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;合并后：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;mainOCD
&lt;/div&gt;&lt;div&gt;mainOCD
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;两张图中的 &lt;code&gt;O&lt;/code&gt;、&lt;code&gt;C&lt;/code&gt;、&lt;code&gt;D&lt;/code&gt; 是同一批 commit。前一张图里 &lt;code&gt;main&lt;/code&gt; 留在 &lt;code&gt;O&lt;/code&gt;；后一张只画发生变化的 &lt;code&gt;main&lt;/code&gt;，它已经抵达 &lt;code&gt;D&lt;/code&gt;。&lt;code&gt;topic&lt;/code&gt; 没有移动，仍然指向 &lt;code&gt;D&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;code&gt;O&lt;/code&gt; 是 &lt;code&gt;D&lt;/code&gt; 的祖先，所以 &lt;code&gt;git merge topic&lt;/code&gt; 不必创建 merge commit。Git 只把 &lt;code&gt;main&lt;/code&gt; 从 &lt;code&gt;O&lt;/code&gt; 移到 &lt;code&gt;D&lt;/code&gt;，commit 图一个节点都没增加；这就是 fast-forward，缩写为 ff。之后看到的 &lt;code&gt;pull --ff-only&lt;/code&gt; 和 push 检查都在问同一个祖先关系。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;merge&lt;/code&gt;：保留分叉，增加汇合点&lt;a href=&quot;#merge保留分叉增加汇合点&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在 &lt;code&gt;main&lt;/code&gt; 上执行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;switch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;merge&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;topic&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;若不能 fast-forward，Git 创建一个 merge commit &lt;code&gt;M&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;maintopicOABCDM
&lt;/div&gt;&lt;div&gt;maintopicOABCDM
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;M&lt;/code&gt; 有两个 parent。历史明确记录两条工作线曾经并行存在。&lt;/p&gt;&lt;p&gt;我会在需要保留集成边界时使用 merge，例如合入一个完整功能分支。想明确保留这个边界，可以：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;merge&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--no-ff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;topic&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;rebase&lt;/code&gt;：复制 commit，换一组 parent&lt;a href=&quot;#rebase复制-commit换一组-parent&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在 &lt;code&gt;topic&lt;/code&gt; 上执行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;switch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;topic&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rebase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Git 找到共同祖先 &lt;code&gt;O&lt;/code&gt;，取出 &lt;code&gt;C&lt;/code&gt;、&lt;code&gt;D&lt;/code&gt; 引入的补丁，再以 &lt;code&gt;B&lt;/code&gt; 为新基底重放，得到新 commit &lt;code&gt;C&apos;&lt;/code&gt;、&lt;code&gt;D&apos;&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;maintopicOABC&apos;D&apos;
&lt;/div&gt;&lt;div&gt;maintopicOABC&apos;D&apos;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;C&apos;&lt;/code&gt; 与 &lt;code&gt;C&lt;/code&gt; 的 parent 不同，所以 ID 必然不同；&lt;code&gt;D&apos;&lt;/code&gt; 的 parent 又变了，因此也有新 ID。rebase 不是”移动原 commit”，而是复制出一条语义相似的新历史，再移动分支引用。&lt;/p&gt;&lt;p&gt;我的规则很简单：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;尚未共享的个人分支，可以 rebase、&lt;code&gt;commit --amend&lt;/code&gt;、&lt;code&gt;rebase --interactive&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;已被其他人基于其开发的公开历史，不随意 rebase；&lt;/li&gt;
&lt;li&gt;集成策略服从项目，不把”线性历史”当宗教。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;交互式整理常用：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rebase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--interactive&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;base&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如果评审后只想把修补自动折叠进原 commit：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--fixup=&amp;lt;commit&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rebase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--interactive&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--autosquash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;base&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;rebase 前后想确认 patch series 是否意外变化：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range-diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;old-base&amp;gt;..&amp;lt;old-tip&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;new-base&amp;gt;..&amp;lt;new-tip&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;cherry-pick&lt;/code&gt;：复制选定的 commit&lt;a href=&quot;#cherry-pick复制选定的-commit&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cherry-pick&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;commit&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它把某个 commit 引入的变化应用到当前 &lt;code&gt;HEAD&lt;/code&gt;，再创建一个新 commit。因为 parent 通常不同，新 ID 也不同。适合把一个独立修复移到维护分支，不适合长期用来手工同步两条本应合并的分支。&lt;/p&gt;&lt;p&gt;冲突不是文件坏了，而是 Git 无法自动决定 3 方合并结果。先看：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;解决每个文件后暂存，再继续对应操作：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;resolved-path&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;merge&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--continue&lt;/span&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt;# merge 场景&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rebase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--continue&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# rebase 场景&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cherry-pick&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--continue&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;# cherry-pick 场景&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;不想继续时，使用相应的 &lt;code&gt;--abort&lt;/code&gt;，不要边慌边 &lt;code&gt;reset --hard&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;远端不是云端魔法，只是另一个仓库&lt;a href=&quot;#远端不是云端魔法只是另一个仓库&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;origin&lt;/code&gt; 只是远端的默认昵称，不是 Git 的保留关键字。查看配置：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;remote&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--verbose&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;remote&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get-url&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;一个本地仓库通常同时有：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;refs/heads/main                 # 我的本地分支&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;refs/remotes/origin/main        # 我上次 fetch 后看到的远端状态&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;远端仓库的 refs/heads/main       # 远端此刻真正的分支&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;后两者不是同一个东西。&lt;code&gt;origin/main&lt;/code&gt; 是本地的远端跟踪引用，它不会凭空知道服务器刚刚发生了什么。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;远端仓库 origin&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;本地仓库 .git&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;fetch 缺少的对象&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;fetch 更新观察值&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;merge 或 rebase&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;push 缺少的对象&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;push 请求更新 ref&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;objects&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/heads/main&lt;br /&gt;&lt;br /&gt;本地 main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/remotes/origin/main&lt;br /&gt;&lt;br /&gt;上次 fetch 的观察值&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;objects&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/heads/main&lt;br /&gt;&lt;br /&gt;远端此刻的 main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;远端仓库 origin&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;本地仓库 .git&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;fetch 缺少的对象&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;fetch 更新观察值&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;merge 或 rebase&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;push 缺少的对象&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;push 请求更新 ref&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;objects&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/heads/main&lt;br /&gt;&lt;br /&gt;本地 main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/remotes/origin/main&lt;br /&gt;&lt;br /&gt;上次 fetch 的观察值&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;objects&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/heads/main&lt;br /&gt;&lt;br /&gt;远端此刻的 main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;关键是不把 &lt;code&gt;origin/main&lt;/code&gt; 画到服务器里。它住在我的 &lt;code&gt;.git&lt;/code&gt; 中，是一次成功 fetch 留下的本地观察值；网络断开以后，它当然不会自己刷新。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;fetch&lt;/code&gt;：下载对象，更新远端跟踪引用&lt;a href=&quot;#fetch下载对象更新远端跟踪引用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--prune&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它与远端协商本地缺少的对象，接收 packfile，再按 refspec 更新 &lt;code&gt;refs/remotes/origin/*&lt;/code&gt;。&lt;code&gt;--prune&lt;/code&gt; 删除远端已经不存在的远端跟踪引用，但不会删除我的本地分支。&lt;/p&gt;&lt;p&gt;fetch 通常不改当前工作区、index 或本地 &lt;code&gt;main&lt;/code&gt;，所以它是理解远端变化的好分界点：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--oneline&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--graph&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--decorate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--all&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--left-right&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cherry-pick&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main...origin/main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main...origin/main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;pull&lt;/code&gt;：先 fetch，再选择一种集成策略&lt;a href=&quot;#pull先-fetch再选择一种集成策略&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;git pull&lt;/code&gt; 不是与 fetch 并列的下载协议。它先 fetch，然后把上游分支集成进当前分支。真正需要决定的是第二步。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;1. fetch&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ff-only&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;rebase&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;no-rebase&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;远端 upstream&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;本地 origin/main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;2. 如何集成？&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;只允许移动 main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;复制本地 commit 到新基底&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;merge，必要时创建 M&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;本地 main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;1. fetch&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ff-only&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;rebase&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;no-rebase&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;远端 upstream&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;本地 origin/main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;2. 如何集成？&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;只允许移动 main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;复制本地 commit 到新基底&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;merge，必要时创建 M&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;本地 main&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;所以 pull 不是第四种历史操作。它只是把 fetch 与某一种已有的集成操作串起来。&lt;/p&gt;&lt;p&gt;我不喜欢让一个无参数 &lt;code&gt;git pull&lt;/code&gt; 隐藏策略。现代、安全而可读的写法是明确选择：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pull&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--ff-only&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# 只允许快进，分叉就停下&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pull&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--rebase&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# fetch 后把本地 commit 重放到上游&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pull&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--no-rebase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;# fetch 后 merge&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;对于还在建立心智模型的人，我更推荐拆成两步：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--prune&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--oneline&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--graph&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--decorate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--all&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rebase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin/main&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# 或 git merge origin/main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这样每个命令只做一类事，出错时也知道哪一层出了问题。Git 版本之间默认 pull 策略可能变化，项目策略也不同；显式参数比背默认值可靠。&lt;/p&gt;&lt;p&gt;长期配置应选自己和团队真正采用的策略，例如：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pull.ff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;only&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 或&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pull.rebase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;不要把两条都照抄。&lt;code&gt;ff-only&lt;/code&gt; 在本地与上游分叉时停止；&lt;code&gt;rebase&lt;/code&gt; 会重写本地尚未发布的 commit。它们表达不同政策。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;push&lt;/code&gt;：上传缺少的对象，请远端移动 ref&lt;a href=&quot;#push上传缺少的对象请远端移动-ref&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;第一次发布分支：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--set-upstream&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;topic&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--set-upstream&lt;/code&gt;（缩写 &lt;code&gt;-u&lt;/code&gt;）建立本地 &lt;code&gt;topic&lt;/code&gt; 与上游分支的关系。以后在 &lt;code&gt;push.default=simple&lt;/code&gt; 的常见配置下，可以直接：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;push 分成两件事：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;把远端缺少的可达对象发送过去；&lt;/li&gt;
&lt;li&gt;请求远端把某个 ref 从旧 ID 更新到新 ID。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;普通 push 默认要求更新是 fast-forward，也就是远端旧 tip 必须是新 tip 的祖先。否则 Git 拒绝 non-fast-forward，因为直接移动 ref 可能让别人已发布的 commit 失去分支入口。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;是&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;否&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;push：远端旧 tip R -&amp;gt; 本地新 tip N&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;R 是 N 的祖先吗？&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;fast-forward&lt;br /&gt;&lt;br /&gt;允许把远端 ref 移到 N&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;non-fast-forward&lt;br /&gt;&lt;br /&gt;默认拒绝&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;先 fetch 并集成远端历史&lt;br /&gt;&lt;br /&gt;或确认后使用 force-with-lease&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;是&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;否&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;push：远端旧 tip R -&amp;gt; 本地新 tip N&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;R 是 N 的祖先吗？&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;fast-forward&lt;br /&gt;&lt;br /&gt;允许把远端 ref 移到 N&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;non-fast-forward&lt;br /&gt;&lt;br /&gt;默认拒绝&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;先 fetch 并集成远端历史&lt;br /&gt;&lt;br /&gt;或确认后使用 force-with-lease&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;服务器检查的是可达关系，不是比较谁的时间戳更新。如果远端 tip 不是本地 tip 的祖先，直接移动 ref 就可能把一段别人仍需要的历史甩出分支可达范围。&lt;/p&gt;&lt;p&gt;如果我确实 rebase 了&lt;strong&gt;自己的&lt;/strong&gt;远端分支，需要改写它，我使用：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--force-with-lease&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它比 &lt;code&gt;--force&lt;/code&gt; 多一个租约条件：远端 ref 必须仍是我预期的值，否则拒绝覆盖。需要最严格地表达预期值时，可以写：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--force-with-lease=refs/heads/topic:&amp;lt;expected-old-oid&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;topic&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;裸 &lt;code&gt;--force-with-lease&lt;/code&gt; 通常以本地远端跟踪引用作为预期值；后台自动 fetch 可能更新这个依据，所以它不是绝对保险。&lt;code&gt;--force&lt;/code&gt; 则直接关闭这层保护，可能丢掉远端 commit。我不会把 &lt;code&gt;push -f&lt;/code&gt; 做成 shell alias，这种便利与拔掉烟雾报警器差不多。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;撤销操作：先问要改哪一层&lt;a href=&quot;#撤销操作先问要改哪一层&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Git 最让人害怕的地方通常不是保存，而是撤销。只要先回答两个问题，命令会清楚很多：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;要改工作区、index、branch ref，还是追加一个反向 commit？&lt;/li&gt;
&lt;li&gt;目标历史是否已经共享？&lt;/li&gt;
&lt;/ol&gt;&lt;section&gt;&lt;h3&gt;常用决策表&lt;a href=&quot;#常用决策表&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;意图&lt;/th&gt;&lt;th&gt;推荐命令&lt;/th&gt;&lt;th&gt;改动层&lt;/th&gt;&lt;th&gt;是否改写 branch 历史&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;丢弃未暂存的路径修改&lt;/td&gt;&lt;td&gt;&lt;code&gt;git restore &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;工作区&lt;/td&gt;&lt;td&gt;否&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;取消暂存并保留工作区&lt;/td&gt;&lt;td&gt;&lt;code&gt;git restore --staged &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;index&lt;/td&gt;&lt;td&gt;否&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;从某快照恢复路径&lt;/td&gt;&lt;td&gt;&lt;code&gt;git restore --source=&amp;lt;rev&amp;gt; &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;工作区&lt;/td&gt;&lt;td&gt;否&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;修正最后一次未发布提交&lt;/td&gt;&lt;td&gt;&lt;code&gt;git commit --amend&lt;/code&gt;&lt;/td&gt;&lt;td&gt;commit + ref&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;撤销已发布提交&lt;/td&gt;&lt;td&gt;&lt;code&gt;git revert &amp;lt;commit&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;新增反向 commit&lt;/td&gt;&lt;td&gt;否&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;本地分支退回但保留暂存&lt;/td&gt;&lt;td&gt;&lt;code&gt;git reset --soft &amp;lt;rev&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;ref&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;本地分支退回并重置 index&lt;/td&gt;&lt;td&gt;&lt;code&gt;git reset --mixed &amp;lt;rev&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;ref + index&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;本地分支、index、工作区全退回&lt;/td&gt;&lt;td&gt;&lt;code&gt;git reset --hard &amp;lt;rev&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;三者&lt;/td&gt;&lt;td&gt;是，并丢工作区修改&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;找回刚刚移丢的 commit&lt;/td&gt;&lt;td&gt;&lt;code&gt;git reflog&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只查看日志&lt;/td&gt;&lt;td&gt;否&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;code&gt;restore&lt;/code&gt; 面向路径，&lt;code&gt;reset&lt;/code&gt; 主要移动当前 branch 并按模式决定是否同步 index 和工作区，&lt;code&gt;revert&lt;/code&gt; 则创建新 commit。名字相似，历史兼容性完全不同。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;revert&lt;/code&gt; 为什么适合公开历史&lt;a href=&quot;#revert-为什么适合公开历史&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;revert&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;bad-commit&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它不删除坏 commit，而是计算反向变化并创建一个新 commit。所有协作者仍共享同一条已有历史，只需继续 fast-forward。对于已经推送到公共分支的错误，这通常是最无聊也最安全的方案。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;reflog：引用移动的本地黑匣子&lt;a href=&quot;#reflog引用移动的本地黑匣子&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;我做了一个故意的事故：先记住第二个 commit &lt;code&gt;0063127&lt;/code&gt;，再把 &lt;code&gt;main&lt;/code&gt; 硬重置回第一个 commit：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reset&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--hard&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cd5e714&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;git log&lt;/code&gt; 看不到第二个 commit 了，但 &lt;code&gt;git reflog --oneline&lt;/code&gt; 仍显示：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd5e714 HEAD@{0}: reset: moving to cd5e714...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;0063127 HEAD@{1}: commit: feat: advance the app&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd5e714 HEAD@{2}: commit (initial): feat: build the first snapshot&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;于是我恢复：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reset&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--hard&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0063127&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;main&lt;/code&gt; 又回到了第二个 commit。reflog 记录启用了日志的 refs 与 &lt;code&gt;HEAD&lt;/code&gt; 的近期移动；普通非裸仓库通常会记录 &lt;code&gt;HEAD&lt;/code&gt; 和本地分支，但不是所有引用都必然有 reflog。它不会 push 到远端，也不是永久备份；条目会过期，不可达对象最终可能被垃圾回收。但在误操作之后，第一反应应该是停止继续改写，运行 &lt;code&gt;git reflog&lt;/code&gt;，给目标 commit 新建分支：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;branch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rescue&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0063127&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这条更稳妥的恢复路径最终得到下面这张历史图：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;mainrescuecd5e7140063127
&lt;/div&gt;&lt;div&gt;mainrescuecd5e7140063127
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;main&lt;/code&gt; 仍停在 &lt;code&gt;cd5e714&lt;/code&gt;，而新建的 &lt;code&gt;rescue&lt;/code&gt; 重新指向 &lt;code&gt;0063127&lt;/code&gt;。这张图描述的是恢复后的 commit DAG，不是重新执行 commit 的过程：&lt;code&gt;git branch rescue 0063127&lt;/code&gt; 只补回一个引用，没有创建 &lt;code&gt;0063127&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;只要对象还在，名字丢了通常比内容丢了容易修。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;图里的 reflog 是恢复窗口，不是永久引用或远端备份。不要把”现在还能找回”理解成”永远不会被回收”。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Git 为什么不会为每个版本保存一整份大文件&lt;a href=&quot;#git-为什么不会为每个版本保存一整份大文件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;为了观察物理存储，我创建了一个 255000 字节文本文件，提交后只追加 15 字节，再提交一次。两个 blob 的逻辑大小分别是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;255000 bytes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;255015 bytes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;在运行 &lt;code&gt;git gc&lt;/code&gt; 前，实验仓库有 15 个 loose objects，zlib 压缩后合计 44641 字节。运行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;verify-pack&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.git/objects/pack/&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;.idx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;packfile 变成 14239 字节，索引为 1492 字节。&lt;code&gt;verify-pack&lt;/code&gt; 显示较新的 255015 字节 blob 在 pack 中占 13080 字节，旧版本则被表示成深度 1 的 delta，数据只有 15 字节（加上对象编码后占 26 字节）。仅比较原 44641 字节 loose objects 与 14239 字节 pack，主体存储缩小约 68.1%。&lt;/p&gt;&lt;p&gt;这解释了两个看似矛盾的事实：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Git 的逻辑模型是完整快照；&lt;/li&gt;
&lt;li&gt;Git 的物理 packfile 可以跨相似对象做 delta compression。&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;packfile：一种物理编码&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;逻辑模型：两个完整 blob&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ID 不变&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ID 不变&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;blob A&lt;br /&gt;&lt;br /&gt;255000 bytes&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;blob B&lt;br /&gt;&lt;br /&gt;255015 bytes&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;保存 blob B 的基底&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;blob A 的 delta&lt;br /&gt;&lt;br /&gt;数据 15 bytes&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;packfile：一种物理编码&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;逻辑模型：两个完整 blob&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ID 不变&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ID 不变&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;blob A&lt;br /&gt;&lt;br /&gt;255000 bytes&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;blob B&lt;br /&gt;&lt;br /&gt;255015 bytes&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;保存 blob B 的基底&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;blob A 的 delta&lt;br /&gt;&lt;br /&gt;数据 15 bytes&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;左边回答”对象是什么”，右边回答”这次 pack 如何省空间”。重新打包可以更换基底和 delta 链，却不能改动对象按完整内容计算出的 ID。&lt;/p&gt;&lt;p&gt;对象的 ID 仍由规范化的完整内容决定，不由”它是哪个对象的 delta”决定。Git 可以随时重新打包而不改变 commit ID。&lt;code&gt;git gc&lt;/code&gt;、push 和服务端维护可能触发打包；日常不需要手工管理每个 pack。&lt;/p&gt;&lt;p&gt;我最后运行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fsck&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--full&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;命令没有输出并以 0 退出，表示这份小仓库的对象连通性和有效性检查通过。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;clone 到底复制了什么&lt;a href=&quot;#clone-到底复制了什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;url&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;project&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;概念上它做了这些事：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;创建本地仓库；&lt;/li&gt;
&lt;li&gt;配置默认远端 &lt;code&gt;origin&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;与远端协商并下载所需对象；&lt;/li&gt;
&lt;li&gt;建立远端跟踪引用；&lt;/li&gt;
&lt;li&gt;创建并检出默认本地分支。&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;工作区本地 .git远端仓库Git 客户端工作区本地 .git远端仓库Git 客户端我git clone URL project创建仓库并配置 origin协商 refs 与缺少的对象发送 packfile 与远端 ref 值建立 origin/main 与本地 main检出默认分支快照我
&lt;/div&gt;&lt;div&gt;工作区本地 .git远端仓库Git 客户端工作区本地 .git远端仓库Git 客户端我git clone URL project创建仓库并配置 origin协商 refs 与缺少的对象发送 packfile 与远端 ref 值建立 origin/main 与本地 main检出默认分支快照我
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;clone 之后出现的是一个能独立工作的本地仓库，而不是远端文件夹的网络映射。&lt;code&gt;origin&lt;/code&gt; 只是把这个新仓库与来源仓库重新连起来的配置。&lt;/p&gt;&lt;p&gt;Git 是分布式版本控制系统，因为普通 clone 通常拥有项目历史和对象数据库，不只是服务器工作区的一个薄视图。离线时我仍能 log、diff、branch、commit、merge 和 rebase；只有与别的仓库交换对象和 refs 时才需要网络。&lt;/p&gt;&lt;p&gt;大仓库可以选择浅克隆、部分克隆或稀疏检出，但三者解决不同问题：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--depth=1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;url&amp;gt;&lt;/span&gt;&lt;span&gt;           &lt;/span&gt;&lt;span&gt;# 截断历史深度&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--filter=blob:none&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;url&amp;gt;&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# 按需获取 blob&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sparse-checkout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 限制工作区展开路径&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;不要把它们统称为”只下载一部分代码”。浅克隆改变可用历史，partial clone 延迟对象获取，sparse-checkout 主要改变工作区呈现。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;多任务开发：&lt;code&gt;worktree&lt;/code&gt; 比反复 stash 更直接&lt;a href=&quot;#多任务开发worktree-比反复-stash-更直接&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;当我正在 &lt;code&gt;feature&lt;/code&gt; 分支写到一半，突然要修 &lt;code&gt;main&lt;/code&gt; 上的线上 bug，传统动作常常是 stash、switch、修复、切回、pop，然后祈祷 stash 冲突别来凑热闹。&lt;/p&gt;&lt;p&gt;更清楚的方式是附加工作树：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;worktree&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-b&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hotfix&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;../project-hotfix&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;同一个仓库对象数据库现在对应两个工作目录：原目录继续停在 &lt;code&gt;feature&lt;/code&gt;，新目录检出 &lt;code&gt;hotfix&lt;/code&gt;。两边有各自的 &lt;code&gt;HEAD&lt;/code&gt; 和 index，共享 objects 和大部分 refs。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;project-hotfix/：附加 worktree&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;project/：主 worktree&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;共享的 common Git dir&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;checkout / restore&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;add&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;checkout / restore&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;add&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;读写共享对象&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;读写共享对象&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;objects&lt;br /&gt;&lt;br /&gt;全部 commit、tree、blob&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/heads/feature&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/heads/hotfix&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;HEAD&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;独立 index&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;工作目录&lt;br /&gt;&lt;br /&gt;未完成的 feature 修改&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;HEAD&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;独立 index&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;工作目录&lt;br /&gt;&lt;br /&gt;hotfix 修改&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;project-hotfix/：附加 worktree&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;project/：主 worktree&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;共享的 common Git dir&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;checkout / restore&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;add&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;checkout / restore&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;add&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;读写共享对象&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;读写共享对象&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;objects&lt;br /&gt;&lt;br /&gt;全部 commit、tree、blob&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/heads/feature&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;refs/heads/hotfix&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;HEAD&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;独立 index&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;工作目录&lt;br /&gt;&lt;br /&gt;未完成的 feature 修改&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;HEAD&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;独立 index&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;工作目录&lt;br /&gt;&lt;br /&gt;hotfix 修改&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;worktree&lt;/code&gt; 不是再 clone 一次。昂贵的对象数据库和大部分 refs 只保留一份；每个工作树只增加自己的工作目录、&lt;code&gt;HEAD&lt;/code&gt;、index 与少量管理状态。于是两个任务可以同时脏、同时运行构建，却不会争夺同一份 checkout。&lt;/p&gt;&lt;p&gt;同一个本地 branch 默认不能同时检出到两个 worktree。这层保护避免两个目录都试图移动同一个 branch ref，却给人一种自己互不相关的错觉。&lt;/p&gt;&lt;p&gt;完成后再移除附加工作树：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;worktree&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;remove&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;../project-hotfix&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这对并行运行测试、比较两个版本，以及让多个 AI agent 各自在独立分支工作尤其有用。它不是复制两份完整仓库，也避免一个工作区被不同任务来回覆盖。&lt;/p&gt;&lt;p&gt;&lt;code&gt;git stash&lt;/code&gt; 仍适合很短的临时收纳：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--include-untracked&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;wip: parser experiment&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;show&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--patch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stash@{&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;但 stash 本质上仍由 commit-like 对象和 refs 管理，不是神秘抽屉。我会给它消息，并尽快处理；需要跨天保存或交给别人时，临时分支上的普通 commit 更可见、更可审查。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一套我现在愿意长期使用的命令行&lt;a href=&quot;#一套我现在愿意长期使用的命令行&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;初始配置&lt;a href=&quot;#初始配置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user.name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;Your Name&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user.email&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;you@example.com&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init.defaultBranch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch.prune&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;merge.conflictStyle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;zdiff3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rerere.enabled&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里前两项进入 commit 身份；&lt;code&gt;fetch.prune&lt;/code&gt; 清理失效的远端跟踪引用；&lt;code&gt;zdiff3&lt;/code&gt; 在冲突标记中显示 base，帮助判断双方分别改了什么；&lt;code&gt;rerere&lt;/code&gt; 记录冲突解决结果，在相同冲突再次出现时复用。签名、凭据管理、换行符和 pull 策略依平台与团队而定，不适合复制一份”万能配置”。&lt;/p&gt;&lt;p&gt;检查配置来源比只看最终值更有用：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--list&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--show-origin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;每日开始&lt;a href=&quot;#每日开始&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;switch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--prune&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pull&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--ff-only&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;switch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;feature/readable-name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如果项目要求 rebase 工作流，我会显式采用它，而不是同时配置互相冲突的习惯。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;制作原子 commit&lt;a href=&quot;#制作原子-commit&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--short&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--branch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--patch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--staged&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;commit message 解释变化的意图和原因。标题能独立读懂，正文写约束、权衡和行为变化；不要把 &lt;code&gt;update files&lt;/code&gt; 这种只能证明键盘工作过的句子留给历史。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;同步并复核&lt;a href=&quot;#同步并复核&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--prune&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rebase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin/main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range-diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin/main...@{upstream}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin/main...HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--set-upstream&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;@{upstream}&lt;/code&gt; 需要当前分支已经配置 upstream；首次发布前没有它。&lt;code&gt;HEAD&lt;/code&gt; 作为 push 源表示当前分支 tip，但目标命名和权限仍由 refspec、配置和服务端规则决定。团队若使用 merge，则换成对应流程，不要为了套模板偷偷改写历史。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;阅读历史&lt;a href=&quot;#阅读历史&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--oneline&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--graph&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--decorate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--all&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--first-parent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;show&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;commit&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;a&amp;gt;..&amp;lt;b&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;blame&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-L&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;start&amp;gt;,&amp;lt;end&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;path&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;blame&lt;/code&gt; 回答”这一行最后由哪个 commit 引入”，不是”该怪谁”。拿到 commit 后继续 &lt;code&gt;git show&lt;/code&gt; 和阅读上下文，通常才有答案。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;定位引入 bug 的 commit&lt;a href=&quot;#定位引入-bug-的-commit&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;当我有一个可重复判定好坏的命令时，&lt;code&gt;bisect&lt;/code&gt; 通常能在 &lt;span&gt;&lt;span&gt;NN&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 个 commit 中用大约 &lt;span&gt;&lt;span&gt;log⁡2N\log_2 N&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;lo&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 次判断定位边界。在线性、每个候选都可测试的理想历史中，1024 个候选 commit 约需 10 轮，而不是从头读 1024 次 diff；复杂 merge DAG 和被跳过的 commit 会增加轮数，甚至让 Git 无法唯一定位。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bisect&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bisect&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bad&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bisect&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;good&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;known-good&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bisect&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./reproduce.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bisect&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reset&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;reproduce.sh&lt;/code&gt; 必须以 0 表示 good，1 到 127（除 125）表示 bad，125 表示无法测试。这个脚本值得认真写，因为错误的 oracle 会高效地把我带到错误答案，计算机在这方面一如既往地非常配合。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;哪些旧习惯该换，哪些只是需要理解&lt;a href=&quot;#哪些旧习惯该换哪些只是需要理解&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;

















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;旧习惯或高风险写法&lt;/th&gt;&lt;th&gt;我现在使用&lt;/th&gt;&lt;th&gt;原因&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git checkout &amp;lt;branch&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git switch &amp;lt;branch&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;明确表达切分支&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git checkout -- &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git restore &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;明确表达覆盖工作区路径&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git reset HEAD &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git restore --staged &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;明确表达取消暂存&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;无参数 &lt;code&gt;git pull&lt;/code&gt;&lt;/td&gt;&lt;td&gt;显式 &lt;code&gt;--ff-only&lt;/code&gt;、&lt;code&gt;--rebase&lt;/code&gt; 或 &lt;code&gt;--no-rebase&lt;/code&gt;&lt;/td&gt;&lt;td&gt;明确集成策略&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git push --force&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git push --force-with-lease&lt;/code&gt;&lt;/td&gt;&lt;td&gt;避免覆盖意外出现的远端更新&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git filter-branch&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git filter-repo&lt;/code&gt; 等专用工具&lt;/td&gt;&lt;td&gt;官方文档明确警告其安全和性能陷阱&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;用 stash 在任务间来回切&lt;/td&gt;&lt;td&gt;&lt;code&gt;git worktree add&lt;/code&gt;&lt;/td&gt;&lt;td&gt;两个工作目录保持独立 index 与工作状态&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;一次 &lt;code&gt;git add .&lt;/code&gt; 后盲提交&lt;/td&gt;&lt;td&gt;&lt;code&gt;add -p&lt;/code&gt; + &lt;code&gt;diff --staged&lt;/code&gt;&lt;/td&gt;&lt;td&gt;设计并验证 commit 边界&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这里最重要的修正是：&lt;strong&gt;旧不等于废弃，现代也不等于更短。&lt;/strong&gt; &lt;code&gt;checkout&lt;/code&gt;、&lt;code&gt;reset&lt;/code&gt; 和 merge 都仍然是合法且必要的 Git 概念。我要淘汰的是含糊意图和无保护的危险默认，而不是为了追新把能工作的命令全部换皮。&lt;/p&gt;&lt;p&gt;&lt;code&gt;git filter-branch&lt;/code&gt; 是少数官方文档直接写着”不推荐使用”的例子；文档建议改用 &lt;code&gt;git filter-repo&lt;/code&gt; 等替代工具。反过来，&lt;code&gt;git checkout&lt;/code&gt; 并没有这种弃用声明。准确区分”职责过载，所以有更清晰的新命令”和”官方明确不推荐”，比传播一张真假混合的现代化清单更重要。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;把 AI 放回正确的位置&lt;a href=&quot;#把-ai-放回正确的位置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;理解这些内部结构后，我并不打算少用 AI。恰恰相反，我可以给出更准确、也更可验证的任务：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;请先展示工作区与 index 的 diff，只暂存解析器修复相关 hunk；&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;创建一个原子 commit；fetch 后不要直接 pull；&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;比较当前分支与 origin/main，再告诉我应 merge 还是 rebase；&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;禁止 force，确需改写个人远端分支时使用 force-with-lease。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这比”帮我把代码传上去”多了一点字，却把数据损失、历史策略和 commit 边界都变成了显式约束。AI 能替我执行命令，但它不能替我承担仓库策略。至少目前如此，我怀疑以后也仍然如此：工具会越来越善于推断意图，而真正稀缺的东西会变成&lt;strong&gt;能否清楚表达意图，并检查结果是否符合约束&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;如果未来的 Git UI 只剩一个”完成我的修改”按钮，底层大概率仍要回答今天这些问题：下一张快照是什么，哪条引用应该移动，是否允许改写已经共享的历史，远端当前值是否仍符合预期。按钮可以消失，状态机不会。&lt;/p&gt;&lt;p&gt;我现在仍会让 AI 帮我 rebase、拆 commit、找回 reflog 里的对象，甚至写那段无聊的 bisect oracle。但当它说”已完成”时，我至少知道该看 &lt;code&gt;status&lt;/code&gt;、&lt;code&gt;diff --staged&lt;/code&gt;、&lt;code&gt;log --graph&lt;/code&gt; 和 refs，而不是只看终端最后一行是不是绿色。很好，接下来终于可以放心地让机器替我敲命令了。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;继续拆下去&lt;a href=&quot;#继续拆下去&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Internals-Git-Objects&quot; target=&quot;_blank&quot;&gt;Pro Git: Git Internals - Git Objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Internals-Git-References&quot; target=&quot;_blank&quot;&gt;Pro Git: Git Internals - Git References&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Internals-Packfiles&quot; target=&quot;_blank&quot;&gt;Pro Git: Git Internals - Packfiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-switch&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;git switch&lt;/code&gt; 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-restore&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;git restore&lt;/code&gt; 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-pull&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;git pull&lt;/code&gt; 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-push&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;git push&lt;/code&gt; 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-reflog&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;git reflog&lt;/code&gt; 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-worktree&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;git worktree&lt;/code&gt; 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-diff&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;git diff&lt;/code&gt; 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/diff-generate-patch&quot; target=&quot;_blank&quot;&gt;Git patch 输出格式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-filter-branch&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;git filter-branch&lt;/code&gt; 的官方警告&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded></item><item><title>一台不存在的计算机：QEMU 工作原理与实践</title><link>https://bfmhno3.github.io/posts/understanding-qemu/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/understanding-qemu/</guid><description>我用 QEMU 10.2.4 从零启动一个 512 字节 x86 扇区，运行 992 字节 AArch64 ELF，再沿着 machine、TCG/KVM、设备与后端、VirtIO、QMP、GDB、磁盘、网络和 ARM virt 拆开完整系统模拟链。</description><pubDate>Fri, 04 Sep 2026 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;写完 &lt;a href=&quot;/posts/understanding-kconfig/&quot;&gt;Kconfig&lt;/a&gt; 和 &lt;a href=&quot;/posts/understanding-kbuild/&quot;&gt;Kbuild&lt;/a&gt; 之后，我已经能配置、编译并加载一个 Linux 内核模块，却发现实验台中央还摆着一块巨大的黑盒：我会复制一行 &lt;code&gt;qemu-system-x86_64&lt;/code&gt; 命令，但不知道它究竟造了哪台机器、CPU 在哪里执行、磁盘为何既是文件又是设备，也不知道按下 &lt;code&gt;Ctrl+A X&lt;/code&gt; 时到底在和谁说话。于是我写了一个 512 字节启动扇区，把它放进 QEMU 10.2.4，又从 QMP、Device Tree 和一份 992 字节的 AArch64 ELF 反向拆开这台并不存在的计算机。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;TCG&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;KVM&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Guest 指令&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;加速器&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;翻译成 Host 指令&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;让 Host CPU 执行&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Machine 与内存映射&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;设备前端&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;块、网络、字符后端&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;固件或直接内核启动&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;HMP / QMP / GDB&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;TCG&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;KVM&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Guest 指令&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;加速器&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;翻译成 Host 指令&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;让 Host CPU 执行&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Machine 与内存映射&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;设备前端&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;块、网络、字符后端&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;固件或直接内核启动&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;HMP / QMP / GDB&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这张图是本文的地图。QEMU 不是单纯的”虚拟 CPU”，也不是一个好看的虚拟机窗口。它把指令执行、整机拓扑、设备模型、宿主机后端和控制接口拼成一个进程。命令行之所以长，只是因为我们在一行文本里描述了一台计算机。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先把名字拆开&lt;a href=&quot;#先把名字拆开&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;QEMU 最常见的 3 组程序做的是不同事情：&lt;/p&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;程序&lt;/th&gt;&lt;th&gt;输入&lt;/th&gt;&lt;th&gt;模拟范围&lt;/th&gt;&lt;th&gt;典型用途&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;qemu-system-x86_64&lt;/code&gt;、&lt;code&gt;qemu-system-aarch64&lt;/code&gt;&lt;/td&gt;&lt;td&gt;固件、内核、磁盘和设备参数&lt;/td&gt;&lt;td&gt;CPU、内存、总线、中断控制器、设备等整机&lt;/td&gt;&lt;td&gt;启动操作系统、固件和裸机程序&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;qemu-aarch64&lt;/code&gt;、&lt;code&gt;qemu-riscv64&lt;/code&gt;&lt;/td&gt;&lt;td&gt;某个目标架构的用户态 ELF&lt;/td&gt;&lt;td&gt;指令集、系统调用、信号和线程接口&lt;/td&gt;&lt;td&gt;在 x86_64 Linux 上运行或测试异构 Linux 程序&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;qemu-img&lt;/code&gt;&lt;/td&gt;&lt;td&gt;磁盘镜像&lt;/td&gt;&lt;td&gt;不运行 Guest&lt;/td&gt;&lt;td&gt;创建、检查、转换、比较镜像&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;另外 3 个名字经常与它混在一起：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;KVM&lt;/strong&gt; 是 Linux 内核里的虚拟化接口。它让兼容架构的 Guest 指令直接在硬件虚拟化模式中执行，但不负责给我造 UART、网卡和磁盘。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QEMU&lt;/strong&gt; 可以只用 TCG 完成跨架构模拟，也可以调用 KVM 执行 vCPU，同时继续提供设备模型和进程生命周期。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;libvirt&lt;/strong&gt; 是更高一层的管理 API。它探测能力、生成 QEMU 参数并管理网络、存储和生命周期；&lt;code&gt;virsh&lt;/code&gt; 和 virt-manager 通常站在这一层。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以”QEMU 还是 KVM”不是一个很好的二选一。常见的 Linux 虚拟机恰好是 &lt;strong&gt;QEMU + KVM&lt;/strong&gt;：KVM 跑 CPU，QEMU 造其余机器。只写 &lt;code&gt;qemu-system-x86_64&lt;/code&gt; 而不指定加速器时，QEMU 默认使用 TCG；安装了 KVM 并不等于这条命令自动用上它。&lt;/p&gt;&lt;p&gt;本文固定在以下实验环境：&lt;/p&gt;
































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;项目&lt;/th&gt;&lt;th&gt;实验值&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Host&lt;/td&gt;&lt;td&gt;x86_64 Linux&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;QEMU&lt;/td&gt;&lt;td&gt;&lt;code&gt;10.2.4&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GNU Binutils&lt;/td&gt;&lt;td&gt;&lt;code&gt;2.46&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Guest 实验 1&lt;/td&gt;&lt;td&gt;16-bit x86 裸机启动扇区&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Guest 实验 2&lt;/td&gt;&lt;td&gt;AArch64 Linux 用户态静态 ELF&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;加速器&lt;/td&gt;&lt;td&gt;TCG 与 KVM 均实际启动&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;先问正在使用的二进制，而不是问搜索引擎记得哪个版本：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-accel&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-cpu&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-device&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-nic&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-display&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;在我的 QEMU 10.2.4 构建中，x86_64 的 &lt;code&gt;-machine help&lt;/code&gt; 打印 43 行 machine 类型，&lt;code&gt;-device help&lt;/code&gt; 列出 455 个设备模型；AArch64 的 machine 列表则有 113 行。这里包含版本化 machine 和别名，不能直接当成 113 块不同开发板，但它已经很好地解释了为什么背命令行是个坏主意。QEMU 自己就是可查询的硬件目录。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一条命令其实在描述什么？&lt;a href=&quot;#一条命令其实在描述什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我会把 system emulator 的参数按 7 层阅读：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-$ARCH \&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[machine] [cpu] [accelerator] \&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[devices] [backends] [interfaces] [boot]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;












































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层&lt;/th&gt;&lt;th&gt;回答的问题&lt;/th&gt;&lt;th&gt;常见参数&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Machine&lt;/td&gt;&lt;td&gt;这是什么板子，地址图和默认设备是什么？&lt;/td&gt;&lt;td&gt;&lt;code&gt;-machine&lt;/code&gt;、&lt;code&gt;-m&lt;/code&gt;、&lt;code&gt;-smp&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CPU&lt;/td&gt;&lt;td&gt;Guest 看见哪种 CPU 和哪些 feature？&lt;/td&gt;&lt;td&gt;&lt;code&gt;-cpu&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Accelerator&lt;/td&gt;&lt;td&gt;指令由 TCG 翻译还是交给 Hypervisor？&lt;/td&gt;&lt;td&gt;&lt;code&gt;-accel&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Device&lt;/td&gt;&lt;td&gt;Guest 驱动看见什么硬件？&lt;/td&gt;&lt;td&gt;&lt;code&gt;-device&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Backend&lt;/td&gt;&lt;td&gt;设备的数据通向 Host 哪里？&lt;/td&gt;&lt;td&gt;&lt;code&gt;-blockdev&lt;/code&gt;、&lt;code&gt;-netdev&lt;/code&gt;、&lt;code&gt;-chardev&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Interface&lt;/td&gt;&lt;td&gt;人和管理程序怎样观察、控制和调试？&lt;/td&gt;&lt;td&gt;&lt;code&gt;-display&lt;/code&gt;、&lt;code&gt;-serial&lt;/code&gt;、&lt;code&gt;-qmp&lt;/code&gt;、&lt;code&gt;-gdb&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Boot&lt;/td&gt;&lt;td&gt;第一条 Guest 代码来自哪里？&lt;/td&gt;&lt;td&gt;&lt;code&gt;-bios&lt;/code&gt;、&lt;code&gt;-kernel&lt;/code&gt;、&lt;code&gt;-initrd&lt;/code&gt;、&lt;code&gt;-append&lt;/code&gt;、&lt;code&gt;-drive&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这里最有用的分界是 &lt;strong&gt;device 与 backend&lt;/strong&gt;。&lt;code&gt;virtio-net-pci&lt;/code&gt; 是 Guest 能枚举并由驱动绑定的 PCI 设备；&lt;code&gt;user&lt;/code&gt;、&lt;code&gt;tap&lt;/code&gt; 或 &lt;code&gt;passt&lt;/code&gt; 是 Host 侧真正收发包的后端。&lt;code&gt;virtio-blk-pci&lt;/code&gt; 是 Guest 磁盘控制器；raw 文件、qcow2 文件或 Host 块设备是后端。前者像插座，后者像插座后面的电线，只是这个比喻用一次就够了。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我先启动 512 字节，而不是一个发行版&lt;a href=&quot;#我先启动-512-字节而不是一个发行版&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果一上来就启动 Debian，屏幕上会同时出现固件、引导器、内核、initramfs、systemd 和虚拟硬件。每一层都可能成功地遮住上一层。为了只观察 QEMU，我写了一个 BIOS 启动扇区：初始化 COM1，输出一行字，然后向测试退出设备写一个值。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.code16&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.global _start&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;_start:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cli&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;xorw %&lt;/span&gt;&lt;span&gt;ax&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;ax&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw %&lt;/span&gt;&lt;span&gt;ax&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;ds&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw %&lt;/span&gt;&lt;span&gt;ax&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;ss&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$0x7c00&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;sp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt; &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# COM1: 115200 baud, 8N1, FIFO on.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$0x3f9&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;xorb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;outb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$0x3fb&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movb &lt;/span&gt;&lt;span&gt;$0x80&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;outb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$0x3f8&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movb &lt;/span&gt;&lt;span&gt;$0x01&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;outb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$0x3f9&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;xorb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;outb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$0x3fb&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movb &lt;/span&gt;&lt;span&gt;$0x03&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;outb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$0x3fa&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movb &lt;/span&gt;&lt;span&gt;$0xc7&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;outb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$0x3fc&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movb &lt;/span&gt;&lt;span&gt;$0x0b&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;outb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$message&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;si&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;lodsb&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;testb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;jz&lt;/span&gt;&lt;span&gt; done&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;ah&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wait_tx:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$0x3fd&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;inb %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;testb &lt;/span&gt;&lt;span&gt;$0x20&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;jz&lt;/span&gt;&lt;span&gt; wait_tx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movb %&lt;/span&gt;&lt;span&gt;ah&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$0x3f8&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;outb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;jmp&lt;/span&gt;&lt;span&gt; print&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;50&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;done:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;51&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$0xf4&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;52&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movb &lt;/span&gt;&lt;span&gt;$0x10&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;53&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;outb %&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;dx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;54&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;hlt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;55&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;56&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;message:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;57&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.asciz &quot;hello from sector &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;\r\n&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;58&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;59&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.org &lt;/span&gt;&lt;span&gt;510&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;60&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;word&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0xaa55&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;构建时我先链接 ELF，让 &lt;code&gt;message&lt;/code&gt; 得到以 &lt;code&gt;0x7c00&lt;/code&gt; 为基准的正确地址，再只抽取 &lt;code&gt;.text&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--32&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;boot.o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;boot.S&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ld&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;elf_i386&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-Ttext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0x7c00&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;boot.elf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;boot.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;objcopy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-O&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;binary&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--only-section=.text&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;boot.elf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;boot.bin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;stat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--format=&lt;/span&gt;&lt;span&gt;&apos;%n %s bytes&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;boot.bin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;输出刚好是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;boot.bin 512 bytes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;最后 16 字节为：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;0x55 0xaa&lt;/code&gt; 位于偏移 510，BIOS 才把它当成可启动扇区。现在启动：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pc,accel=tcg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-drive&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;file=boot.bin,format=raw,if=floppy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-display&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-serial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stdio&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-monitor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-no-reboot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-device&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;isa-debug-exit,iobase=0xf4,iosize=&lt;/span&gt;&lt;span&gt;0x04&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;实际输出只有一行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;hello from sector 0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;进程退出码为 &lt;code&gt;33&lt;/code&gt;。这不是随机事故。&lt;code&gt;isa-debug-exit&lt;/code&gt; 收到 &lt;code&gt;0x10&lt;/code&gt; 后按 &lt;code&gt;(value &amp;lt;&amp;lt; 1) | 1&lt;/code&gt; 生成退出码，所以得到 &lt;span&gt;&lt;span&gt;0x10×2+1=330x10 \times 2 + 1 = 33&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;×&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;33&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。这个设备不是现实 PC 上的标准零件，而是我明确加进实验机的测试出口。Guest 写一个 I/O port，QEMU 的设备回调收到访问并结束 Host 进程。512 字节代码已经跨过了 CPU、I/O 地址空间、UART、字符后端和进程生命周期 5 层。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;从复位向量到 &lt;code&gt;0x7c00&lt;/code&gt;&lt;a href=&quot;#从复位向量到-0x7c00&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这条路径仍有固件参与：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;Host16550A UARTGuest RAM虚拟软盘SeaBIOSx86 vCPUHost16550A UARTGuest RAM虚拟软盘SeaBIOSx86 vCPU从复位向量开始执行读取第 0 扇区放到物理地址 0x7c00跳到 0x7c00OUT 到 COM1写入 stdio chardev
&lt;/div&gt;&lt;div&gt;Host16550A UARTGuest RAM虚拟软盘SeaBIOSx86 vCPUHost16550A UARTGuest RAM虚拟软盘SeaBIOSx86 vCPU从复位向量开始执行读取第 0 扇区放到物理地址 0x7c00跳到 0x7c00OUT 到 COM1写入 stdio chardev
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;boot.bin&lt;/code&gt; 不是 Host 进程可直接执行的 ELF。SeaBIOS 经虚拟软盘控制器读它，检查签名并复制到 Guest RAM。我的 &lt;code&gt;outb&lt;/code&gt; 也没有直接碰 Host 的物理串口；QEMU 捕获 Guest I/O 指令，把访问路由到 16550A 设备模型，再由 &lt;code&gt;-serial stdio&lt;/code&gt; 连接终端。&lt;/p&gt;&lt;p&gt;我把 &lt;code&gt;-accel tcg&lt;/code&gt; 换成 &lt;code&gt;-accel kvm&lt;/code&gt; 后，同一个扇区也输出同一行并以 33 退出。可观察的 machine contract 没变，变化的是 vCPU 执行引擎。这正是加速器应该有的边界。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;TCG 到底模拟了什么？&lt;a href=&quot;#tcg-到底模拟了什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;TCG 是 Tiny Code Generator。粗略但有用的数据流是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;分支、异常、I/O、失效&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Guest 指令&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Guest 前端解码&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;TCG 中间操作&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Host 后端生成代码&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Translation Block 缓存&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Host CPU 执行&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;分支、异常、I/O、失效&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Guest 指令&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Guest 前端解码&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;TCG 中间操作&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Host 后端生成代码&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Translation Block 缓存&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Host CPU 执行&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;QEMU 通常以 translation block 为单位翻译一段 Guest 指令，而不是每执行一条就启动一次解释器。已经生成的 Host 代码可以缓存和复用；控制流、异常、自修改代码、MMIO 等事件再让执行返回 QEMU。具体边界是实现细节，不能把 TB 简化成 C 语言 basic block 的永久一一映射。&lt;/p&gt;&lt;p&gt;TCG 的价值不是”比硬件快”，而是 &lt;strong&gt;Host 与 Guest 架构可以不同&lt;/strong&gt;。x86_64 Host 能运行 AArch64、RISC-V、MIPS 等 Guest，也能提供较强的可观察性、确定性相关选项和软件断点能力。代价是翻译、状态同步和设备模拟都有成本。&lt;/p&gt;&lt;p&gt;常用写法：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-aarch64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;virt,accel=tcg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-cpu&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-smp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-cpu max&lt;/code&gt; 表示 TCG 能提供的丰富功能集合，不代表某颗真实 SoC。若目标是复现 Cortex-A57 的行为，就应选 &lt;code&gt;-cpu cortex-a57&lt;/code&gt;，并接受这个模型实现了什么、不实现什么。&lt;/p&gt;&lt;p&gt;&lt;code&gt;-accel tcg,thread=multi&lt;/code&gt; 允许支持的目标为多个 vCPU 使用多个 TCG 线程。它不保证 4 个 Guest vCPU 就得到 4 倍性能；锁竞争、设备路径和 Guest 工作负载都在场。&lt;code&gt;one-insn-per-tb=on&lt;/code&gt; 则故意把每个 TB 缩到一条指令，适合分析日志，不适合假装性能还正常（这次真的不能两全）。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;KVM 为什么快，又为什么仍然需要 QEMU？&lt;a href=&quot;#kvm-为什么快又为什么仍然需要-qemu&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;当 Host 与 Guest 架构兼容、CPU 支持硬件虚拟化且 &lt;code&gt;/dev/kvm&lt;/code&gt; 可用时，可以写：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;q35&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-accel&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;kvm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-cpu&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;host&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2G&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-smp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;QEMU 通过 KVM API 创建 VM 和 vCPU，把 Guest RAM 注册给内核，然后让 vCPU 进入硬件虚拟化执行。普通 Guest 指令不必先翻译成 Host 指令；遇到需要 Host 处理的事件，例如某些 I/O、MMIO 或停机状态，KVM 退出到 QEMU，QEMU 再运行对应设备逻辑。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;QEMU device modelHost CPU/dev/kvmQEMUQEMU device modelHost CPU/dev/kvmQEMU创建 VM、vCPU、映射 Guest RAMKVM_RUN进入 Guest modeMMIO/PIO 等 VM exit返回 exit 原因执行设备访问返回结果再次 KVM_RUN
&lt;/div&gt;&lt;div&gt;QEMU device modelHost CPU/dev/kvmQEMUQEMU device modelHost CPU/dev/kvmQEMU创建 VM、vCPU、映射 Guest RAMKVM_RUN进入 Guest modeMMIO/PIO 等 VM exit返回 exit 原因执行设备访问返回结果再次 KVM_RUN
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-cpu host&lt;/code&gt; 暴露 Host CPU 的大量特性，通常适合单机开发和性能实验，却会把 VM 绑得更紧。需要跨不同 Host live migration 时，应选择双方共同支持的命名 CPU model 和 feature 集，而不是默认假设另一台机器也长着同一颗 CPU。&lt;/p&gt;&lt;p&gt;我不会用 512 字节启动扇区比较 TCG 与 KVM 性能。这个 workload 的 Guest 只输出 21 个字符，QEMU 进程启动和固件执行占据了绝大多数时间；测出来的主要是噪声，不是 CPU 加速比。要比较加速器，至少应固定 machine、CPU feature、vCPU 数、Guest 镜像和 workload，再分别测 Guest 内部 CPU 时间、I/O 吞吐与 Host 消耗。&lt;/p&gt;&lt;p&gt;也不要把 KVM 当成安全魔法。QEMU 的设备模型仍然处理 Guest 提交的数据，QEMU 官方文档明确指出 TCG 不是安全边界；测试不可信镜像时仍应使用非特权账户、最小设备集合、受控网络和宿主机沙箱策略。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Machine 不是一个无关紧要的名字&lt;a href=&quot;#machine-不是一个无关紧要的名字&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Machine type 决定主板级拓扑：地址空间、总线、芯片组、中断控制器、固件接口和一组默认设备。CPU 只是板子上的一个节点。&lt;/p&gt;&lt;p&gt;x86_64 常见两族是：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pc-i440fx-*&lt;/code&gt;：较老的 i440FX + PIIX PC 平台，兼容面广。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pc-q35-*&lt;/code&gt;：较现代的 Q35 + ICH9 平台，原生 PCIe 拓扑更符合新设备。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;我的 QEMU 10.2.4 默认项是 &lt;code&gt;pc-i440fx-10.2&lt;/code&gt;，而 &lt;code&gt;pc&lt;/code&gt; 是指向当前默认版本的别名。用于一次性内核实验时，别名很方便；用于长期保存或迁移的 VM 时，应记录版本化 machine，例如 &lt;code&gt;pc-q35-10.2&lt;/code&gt;。升级 QEMU 后，&lt;code&gt;q35&lt;/code&gt; 别名可能指向新兼容级别，而版本化名字存在的目的正是固定 Guest 可见的硬件 contract。&lt;/p&gt;&lt;p&gt;ARM 更能暴露这个事实。x86 PC 长期有相对统一的启动和硬件传统；ARM 镜像往往绑定到具体 SoC、UART 地址、中断控制器和 Device Tree。&lt;code&gt;qemu-system-aarch64&lt;/code&gt; 因此没有默认 machine，必须写：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-aarch64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;virt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;virt&lt;/code&gt; 不是某块真实开发板。它是专门为虚拟化设计的通用平台，提供可配置 CPU、RAM、PL011、GIC、PCI/PCIe、VirtIO 和生成的 Device Tree。若目的是学习 Linux、U-Boot 和驱动框架，它通常比假装某块量产板更干净；若目的是复现真实板卡寄存器和启动 ROM，就必须检查 &lt;code&gt;-machine help&lt;/code&gt; 是否真的包含那块板。没有 machine model 时，一份真实固件不会因为加上 &lt;code&gt;-dtb board.dtb&lt;/code&gt; 就突然能运行。DTB 描述硬件，不负责实现硬件。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我让 QEMU 自己交出 ARM 硬件图&lt;a href=&quot;#我让-qemu-自己交出-arm-硬件图&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;QEMU 可以把 &lt;code&gt;virt&lt;/code&gt; machine 生成的 DTB 写出来：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-aarch64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;virt,dumpdtb=virt.dtb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-cpu&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cortex-a57&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;256M&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-display&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;dtc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-I&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dtb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-O&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;virt.dts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;virt.dtb&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;QEMU 写出的 &lt;code&gt;virt.dtb&lt;/code&gt; 是一个预留到 &lt;code&gt;1,048,576&lt;/code&gt; 字节的 blob。反编译后，关键节点包括：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;memory@40000000 {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;reg = &amp;lt;0x00 0x40000000 0x00 0x10000000&amp;gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;device_type = &quot;memory&quot;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pl011@9000000 {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;interrupts = &amp;lt;0x00 0x01 0x04&amp;gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;reg = &amp;lt;0x00 0x9000000 0x00 0x1000&amp;gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;compatible = &quot;arm,pl011&quot;, &quot;arm,primecell&quot;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;virtio_mmio@a000000 {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;interrupts = &amp;lt;0x00 0x10 0x01&amp;gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;reg = &amp;lt;0x00 0xa000000 0x00 0x200&amp;gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;compatible = &quot;virtio,mmio&quot;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;实验中的 &lt;code&gt;-m 256M&lt;/code&gt; 变成从 &lt;code&gt;0x40000000&lt;/code&gt; 开始、长度 &lt;code&gt;0x10000000&lt;/code&gt; 的 RAM。串口是 &lt;code&gt;0x09000000&lt;/code&gt; 的 PL011，&lt;code&gt;chosen.stdout-path&lt;/code&gt; 也指向它。DTB 还预留了从 &lt;code&gt;0x0a000000&lt;/code&gt; 到 &lt;code&gt;0x0a003e00&lt;/code&gt; 的 32 个 VirtIO MMIO slot，每个间隔 &lt;code&gt;0x200&lt;/code&gt;。命令行不再是一串咒语，它已经变成内核实际消费的硬件描述。&lt;/p&gt;&lt;p&gt;因此 ARM64 串口启动参数通常是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;console=ttyAMA0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;而上一篇 x86_64 实验使用的是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;console=ttyS0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这不是 Linux 随机换了名字。两个 machine 分别提供 PL011 和 16550A 风格 UART，对应不同驱动与设备节点。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;固件启动、磁盘启动和直接内核启动&lt;a href=&quot;#固件启动磁盘启动和直接内核启动&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;QEMU 可以从不同层进入 Guest。它们不是同一条路径的不同拼写。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;1. 固件加磁盘&lt;a href=&quot;#1-固件加磁盘&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;典型 PC VM：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;q35,accel=kvm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2G&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-drive&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;file=system.qcow2,format=qcow2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;QEMU 启动 BIOS 或 UEFI 固件，固件枚举设备并选择启动项，随后进入磁盘里的引导器，再由引导器加载内核。适合安装和运行完整发行版，也最接近通用机器的启动链。&lt;/p&gt;&lt;p&gt;UEFI 的代码镜像通常只读，变量存储则需要单独的可写副本。不要让多个 VM 直接共享同一个可写变量镜像；Secure Boot key、BootOrder 和固件设置都在里面。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. &lt;code&gt;-bios&lt;/code&gt; 启动固件或裸机镜像&lt;a href=&quot;#2--bios-启动固件或裸机镜像&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;对 ARM &lt;code&gt;virt&lt;/code&gt; 上的 U-Boot v2026.07，官方文档给出的非安全启动是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;qemu_arm64_defconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-aarch64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;virt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-cpu&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cortex-a57&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-nographic&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-bios&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;u-boot.bin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里 QEMU 把 U-Boot 放进模拟 flash，从地址 &lt;code&gt;0x0&lt;/code&gt; 执行，并把生成的 DTB 放在 RAM 开头。若要复现 EL3、TF-A 与 OP-TEE，则启动链和镜像组合会继续增加；不能拿一个非安全 &lt;code&gt;u-boot.bin&lt;/code&gt; 命令假装已经验证 secure world。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3. &lt;code&gt;-kernel&lt;/code&gt; 直接内核启动&lt;a href=&quot;#3--kernel-直接内核启动&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;上一篇文章中，我用下面的命令启动自己构建的 Linux 6.18：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;accel=tcg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-cpu&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;256M&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-smp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-nographic&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-no-reboot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-kernel&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arch/x86/boot/bzImage&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-initrd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;initramfs.cpio&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-append&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;console=ttyS0 rdinit=/init panic=-1&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它绕过 Guest 磁盘上的 GRUB 和 root filesystem 查找，直接把 kernel、initramfs 和 command line 交给 QEMU 的直接 Linux boot 路径。它很适合内核迭代：不必每次把 &lt;code&gt;bzImage&lt;/code&gt; 复制进磁盘分区，也不必调试一个与当前问题无关的 bootloader。&lt;/p&gt;&lt;p&gt;ARM64 对应的实验骨架是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-aarch64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;virt,accel=tcg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-cpu&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cortex-a57&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;512M&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-smp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-nographic&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-no-reboot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-kernel&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arch/arm64/boot/Image&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-initrd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;initramfs.cpio&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-append&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;console=ttyAMA0 rdinit=/init panic=-1&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;内核必须真的包含 &lt;code&gt;virt&lt;/code&gt; 所需的 CPU、GIC、PL011、timer、PCI 或 VirtIO 支持。QEMU 能造出设备，不会替 Linux 打开 Kconfig。于是前两篇文章与这一篇形成了闭环：Kconfig 允许驱动存在，Kbuild 把驱动放进镜像，QEMU machine 决定启动后是否真有匹配的硬件。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;-nographic&lt;/code&gt; 不等于 &lt;code&gt;-display none&lt;/code&gt;&lt;a href=&quot;#-nographic-不等于--display-none&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这两个选项看起来接近，行为并不相同：&lt;/p&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;选项&lt;/th&gt;&lt;th&gt;图形窗口&lt;/th&gt;&lt;th&gt;串口&lt;/th&gt;&lt;th&gt;Monitor&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-display none&lt;/code&gt;&lt;/td&gt;&lt;td&gt;不创建显示输出&lt;/td&gt;&lt;td&gt;不自动重定向&lt;/td&gt;&lt;td&gt;不自动重定向&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-nographic&lt;/code&gt;&lt;/td&gt;&lt;td&gt;禁用图形输出&lt;/td&gt;&lt;td&gt;通常把串口接到 stdio&lt;/td&gt;&lt;td&gt;将 Monitor 与串口复用到 stdio&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-serial stdio -monitor none -display none&lt;/code&gt;&lt;/td&gt;&lt;td&gt;无&lt;/td&gt;&lt;td&gt;只把串口接到 stdio&lt;/td&gt;&lt;td&gt;禁用 HMP&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;我在自动实验中偏爱第三种显式写法，因为日志里只有 Guest 串口，不会意外把 &lt;code&gt;(qemu)&lt;/code&gt; Monitor prompt 混进去。交互调试时 &lt;code&gt;-nographic&lt;/code&gt; 很方便：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Ctrl+A H&lt;/code&gt; 查看快捷键。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl+A C&lt;/code&gt; 在串口与 HMP Monitor 之间切换。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ctrl+A X&lt;/code&gt; 退出 QEMU。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这些是 QEMU chardev multiplexer 的转义，不是 Guest 收到的 &lt;code&gt;Ctrl+A&lt;/code&gt;。要把真的 &lt;code&gt;Ctrl+A&lt;/code&gt; 送给 Guest，需要按两次 &lt;code&gt;Ctrl+A&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;code&gt;console=ttyS0&lt;/code&gt; 或 &lt;code&gt;console=ttyAMA0&lt;/code&gt; 同样不可省略。QEMU 把虚拟串口接到终端，只解决 Host 端；Linux command line 还要告诉内核把 console 写到哪个 Guest 设备。两边必须在同一根虚拟电线上见面。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;设备前端与后端必须成对阅读&lt;a href=&quot;#设备前端与后端必须成对阅读&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;考虑网络：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-netdev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user,id=net0,hostfwd=tcp:127.0.0.1:2222-:22&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-device &lt;/span&gt;&lt;span&gt;virtio-net-pci,netdev=net0,mac=52:54:00:12:34:56&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;virtio-net-pci&lt;/code&gt; 是 Guest 枚举到的网卡。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;netdev=net0&lt;/code&gt; 把它连到 ID 为 &lt;code&gt;net0&lt;/code&gt; 的 Host backend。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user&lt;/code&gt; 选择无特权用户态网络栈。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hostfwd&lt;/code&gt; 把 Host &lt;code&gt;127.0.0.1:2222&lt;/code&gt; 转到 Guest TCP 22。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;只写 &lt;code&gt;-netdev&lt;/code&gt; 没有 Guest 网卡，Guest 无从发送；只写带 &lt;code&gt;netdev=net0&lt;/code&gt; 的 &lt;code&gt;-device&lt;/code&gt; 而没有同名 backend，QEMU 会在启动时拒绝这个悬空引用。ID 是这张图里的接线标签。&lt;/p&gt;&lt;p&gt;存储也一样。下面的显式 block graph 经 QEMU 10.2.4 启动验证：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;q35,accel=tcg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-nodefaults&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-display&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-blockdev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;driver=file,node-name=file0,filename=disk.qcow2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-blockdev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;driver=qcow2,node-name=disk0,file=file0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-device&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;virtio-blk-pci,drive=disk0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;数据路径为：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Guest virtio-blk driver&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;virtio-blk-pci device&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;qcow2 block node: disk0&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;file block node: file0&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Host disk.qcow2&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Guest virtio-blk driver&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;virtio-blk-pci device&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;qcow2 block node: disk0&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;file block node: file0&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Host disk.qcow2&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-drive file=...,format=qcow2,if=virtio&lt;/code&gt; 是方便的组合式旧入口；&lt;code&gt;-blockdev&lt;/code&gt; 加 &lt;code&gt;-device&lt;/code&gt; 更啰嗦，却把格式层、文件层和 Guest 设备层分开。要做 overlay、镜像过滤、throttle 或 live block operation 时，显式图更容易审查。&lt;/p&gt;&lt;p&gt;&lt;code&gt;-nodefaults&lt;/code&gt; 会移除 machine 的常见默认设备。它适合可重复实验，但不是”删掉全部硬件”；machine 本身不可缺少的控制器和平台对象仍然存在。搭一台最小 VM 时，我会加它，然后只添加实验需要的串口、磁盘和网卡。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;raw 与 qcow2 不是”大文件与小文件”&lt;a href=&quot;#raw-与-qcow2-不是大文件与小文件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我创建了两个虚拟容量均为 1 GiB 的空镜像：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-img&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;disk.raw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1G&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-img&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;qcow2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;disk.qcow2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1G&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-img&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--output=json&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;disk.qcow2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;在支持 sparse file 的 Host filesystem 上，结果是：&lt;/p&gt;






















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;镜像&lt;/th&gt;&lt;th&gt;Guest 虚拟容量&lt;/th&gt;&lt;th&gt;Host 文件逻辑长度&lt;/th&gt;&lt;th&gt;实际分配空间&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;raw&lt;/td&gt;&lt;td&gt;&lt;code&gt;1,073,741,824&lt;/code&gt; B&lt;/td&gt;&lt;td&gt;&lt;code&gt;1,073,741,824&lt;/code&gt; B&lt;/td&gt;&lt;td&gt;&lt;code&gt;4,096&lt;/code&gt; B&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;qcow2&lt;/td&gt;&lt;td&gt;&lt;code&gt;1,073,741,824&lt;/code&gt; B&lt;/td&gt;&lt;td&gt;&lt;code&gt;196,624&lt;/code&gt; B&lt;/td&gt;&lt;td&gt;&lt;code&gt;200,704&lt;/code&gt; B&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;空 raw 在这里反而只分配 4 KiB，因为它是 sparse file；qcow2 需要约 196 KiB 元数据。于是”qcow2 一定更省空间”在第 0 秒就是错的。两者真正的差别是格式与能力：&lt;/p&gt;







































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;raw&lt;/th&gt;&lt;th&gt;qcow2&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;布局&lt;/td&gt;&lt;td&gt;扇区偏移接近直接映射&lt;/td&gt;&lt;td&gt;L1/L2 table、cluster、refcount 等元数据&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;稀疏&lt;/td&gt;&lt;td&gt;依赖 Host filesystem&lt;/td&gt;&lt;td&gt;格式原生按 cluster 分配&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;backing file&lt;/td&gt;&lt;td&gt;无&lt;/td&gt;&lt;td&gt;有&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;internal snapshot&lt;/td&gt;&lt;td&gt;无&lt;/td&gt;&lt;td&gt;有&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;压缩/加密能力&lt;/td&gt;&lt;td&gt;格式本身无&lt;/td&gt;&lt;td&gt;支持特定模式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;可移植和恢复&lt;/td&gt;&lt;td&gt;简单&lt;/td&gt;&lt;td&gt;需要理解 qcow2 元数据&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;我又创建了 overlay：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-img&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;qcow2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-F&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;qcow2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-b&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PWD&lt;/span&gt;&lt;span&gt;/disk.qcow2&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;overlay.qcow2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它的虚拟容量仍是 1 GiB，初始实际分配 &lt;code&gt;200,704&lt;/code&gt; 字节。读取未覆盖 cluster 时沿 backing chain 去 base，写入则进入 overlay。这个机制非常适合”一份只读基础镜像 + 多个实验分支”。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Backing chain 是数据结构，不是文件命名约定&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;移动、修改或删除 base 可能让 overlay 读到错误数据或直接打不开。创建时显式写 &lt;code&gt;-F&lt;/code&gt;，用 &lt;code&gt;qemu-img info --backing-chain&lt;/code&gt; 检查链，并在分发前用 &lt;code&gt;qemu-img convert&lt;/code&gt; 或 &lt;code&gt;rebase&lt;/code&gt; 按计划扁平化。不要在不理解 &lt;code&gt;rebase -u&lt;/code&gt; 的情况下用它改元数据指针。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;QEMU 里至少有 3 种容易都被叫作”快照”的东西：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;-snapshot&lt;/code&gt;：写入临时 overlay，QEMU 退出后通常丢弃。本质上是一次性运行模式。&lt;/li&gt;
&lt;li&gt;qcow2 internal snapshot：用 &lt;code&gt;savevm&lt;/code&gt;、&lt;code&gt;loadvm&lt;/code&gt;、&lt;code&gt;delvm&lt;/code&gt; 保存磁盘与 VM state，受设备支持限制。&lt;/li&gt;
&lt;li&gt;external snapshot/overlay：新 qcow2 文件以旧镜像为 backing file，链条显式存在，常用于管理系统。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;快照不是备份。Guest filesystem 正在写入时抓到的镜像可能只是 crash-consistent；数据库一致性还需要 Guest 协作、freeze 或应用层 protocol。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;VirtIO 为什么不是”模拟一块更快的真网卡”？&lt;a href=&quot;#virtio-为什么不是模拟一块更快的真网卡&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;QEMU 可以模拟 e1000、AHCI、NVMe 等现实设备，Guest 使用现实硬件驱动。这对兼容性和固件测试很重要，但每次寄存器访问与硬件语义都要模拟。&lt;/p&gt;&lt;p&gt;VirtIO 选择另一条路：它定义一种专为虚拟环境设计的标准设备 contract。Guest 驱动与 device 通过 virtqueue 交换 descriptor，批量描述 buffer，再用 notification 告知对方。它不需要假装自己是某块 20 年前的网卡。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;QEMU/vhost backendVirtqueue in Guest RAMGuest driverQEMU/vhost backendVirtqueue in Guest RAMGuest driver写 descriptor 与 available ringnotification读取 buffer 并处理 I/O更新 used ringinterrupt 或轮询可见
&lt;/div&gt;&lt;div&gt;QEMU/vhost backendVirtqueue in Guest RAMGuest driverQEMU/vhost backendVirtqueue in Guest RAMGuest driver写 descriptor 与 available ringnotification读取 buffer 并处理 I/O更新 used ringinterrupt 或轮询可见
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这里还要区分 &lt;strong&gt;设备类型&lt;/strong&gt; 与 &lt;strong&gt;transport&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;virtio-blk&lt;/code&gt;、&lt;code&gt;virtio-net&lt;/code&gt;、&lt;code&gt;virtio-rng&lt;/code&gt; 描述设备功能。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;virtio-*-pci&lt;/code&gt; 经 PCI capability 暴露。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;virtio-*-device&lt;/code&gt; 在某些 machine 上经 VirtIO MMIO 或其他 bus 暴露。&lt;/li&gt;
&lt;li&gt;split virtqueue 与 packed virtqueue 描述 ring layout，不是两种磁盘格式。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;使用 VirtIO 的前提是 Guest 有对应驱动。initramfs 想从 &lt;code&gt;/dev/vda&lt;/code&gt; 挂载 root 时，&lt;code&gt;CONFIG_VIRTIO_BLK&lt;/code&gt;、transport 和必要的 PCI 支持必须内建，不能全做成等 root filesystem 挂载后才加载的模块。这又回到了 Kconfig，只是现在我们终于知道那个选项在为哪块虚拟硬件服务。&lt;/p&gt;&lt;p&gt;&lt;code&gt;vhost&lt;/code&gt; 还能把部分 VirtIO 数据面移出 QEMU 主循环，例如 &lt;code&gt;vhost-net&lt;/code&gt; 借助内核处理网络数据，&lt;code&gt;vhost-user&lt;/code&gt; 则通过 Unix socket 连接独立 userspace backend。控制面、feature negotiation、内存共享和安全边界会变复杂，不能只因为名字里有 &lt;code&gt;vhost&lt;/code&gt; 就默认打开。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;网络：先用 user，理解后再上 TAP&lt;a href=&quot;#网络先用-user理解后再上-tap&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;最省事的无特权配置是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-netdev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user,id=net0,hostfwd=tcp:127.0.0.1:2222-:22&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-device &lt;/span&gt;&lt;span&gt;virtio-net-pci,netdev=net0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;QEMU user-mode network 的默认拓扑通常是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Guest 10.0.2.15&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&amp;gt; gateway 10.0.2.2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&amp;gt; DNS 10.0.2.3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&amp;gt; Host/Internet&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Guest 位于 NAT 后面，Host 或局域网不能凭空主动连接它，所以 SSH 需要 &lt;code&gt;hostfwd&lt;/code&gt;。绑定 &lt;code&gt;127.0.0.1&lt;/code&gt; 比省略地址更保守，避免意外把实验 SSH 暴露到所有 Host 接口。&lt;/p&gt;&lt;p&gt;ICMP 是一个常见误判点。user backend 对外部 &lt;code&gt;ping&lt;/code&gt; 的支持受 Host 与配置限制，&lt;code&gt;ping&lt;/code&gt; 失败不能单独证明 TCP/DNS 坏了。先在 Guest 检查地址、route、DNS，再用实际要验证的 TCP/UDP 流量测试。&lt;/p&gt;&lt;p&gt;TAP 则在 Host 创建二层接口：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-netdev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tap,id=net0,ifname=tap0,script=no,downscript=no&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-device &lt;/span&gt;&lt;span&gt;virtio-net-pci,netdev=net0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它适合桥接到 Host bridge、构建多 VM 二层网络或做抓包实验，但创建和配置 TAP/bridge 通常需要额外权限。网络连不通时应分别检查：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;Guest NIC 驱动是否绑定。&lt;/li&gt;
&lt;li&gt;Guest link、地址和 route 是否正确。&lt;/li&gt;
&lt;li&gt;QEMU device 是否连到正确 &lt;code&gt;netdev&lt;/code&gt; ID。&lt;/li&gt;
&lt;li&gt;TAP 是否 UP 并加入预期 bridge。&lt;/li&gt;
&lt;li&gt;Host forwarding、防火墙和 NAT 是否允许流量。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;code&gt;passt&lt;/code&gt; 是另一种无特权 backend。它作为独立 daemon 工作，通常比内置 user-mode stack 提供更完整的 IPv6、性能和隔离特性。选择 backend 是网络拓扑与安全决策，不是 VirtIO 驱动决策。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;QEMU user mode：只模拟一个进程&lt;a href=&quot;#qemu-user-mode只模拟一个进程&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;为了确认 &lt;code&gt;qemu-aarch64&lt;/code&gt; 和 system emulator 的边界，我写了一个不用 libc 的 AArch64 程序：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.global _start&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;_start:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mov&lt;/span&gt;&lt;span&gt; x0, #&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;adr x1, message&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mov&lt;/span&gt;&lt;span&gt; x2, #message_end - message&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mov&lt;/span&gt;&lt;span&gt; x8, #&lt;/span&gt;&lt;span&gt;64&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;svc #&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mov&lt;/span&gt;&lt;span&gt; x0, #&lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mov&lt;/span&gt;&lt;span&gt; x8, #&lt;/span&gt;&lt;span&gt;93&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;svc #&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;message:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.ascii &quot;hello from AArch64 user mode\n&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;message_end:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;交叉链接出一个 &lt;code&gt;992&lt;/code&gt; 字节静态 ELF：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;aarch64-linux-gnu-gcc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-nostdlib&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-Wl,--build-id=none&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hello-aarch64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hello-aarch64.S&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hello-aarch64&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;file&lt;/code&gt; 确认它是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ELF 64-bit LSB executable, ARM aarch64, statically linked&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;x86_64 Host 不能原生执行这份指令，但 user emulator 可以：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-aarch64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-strace&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./hello-aarch64&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我实际看到：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;write(1,0x400098,29)hello from AArch64 user mode&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;= 29&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;exit(7)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;进程退出码正是 7。这里没有 AArch64 BIOS、GIC、PL011、磁盘或第二个 Linux kernel。QEMU 翻译 AArch64 用户指令，拦截 &lt;code&gt;svc&lt;/code&gt;，把 Guest ABI 的 &lt;code&gt;write&lt;/code&gt; 和 &lt;code&gt;exit&lt;/code&gt; 参数转换后交给 Host Linux kernel。&lt;/p&gt;&lt;p&gt;动态链接程序还需要目标架构的 loader 与 shared libraries：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-aarch64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-L&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/path/to/aarch64-sysroot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./app&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-L&lt;/code&gt; 指向 Guest ABI sysroot，不是随便一个包含 &lt;code&gt;.so&lt;/code&gt; 的目录。混用错误架构、错误 dynamic linker 路径或不兼容 glibc 时，问题发生在 ABI 与文件系统视图，不在虚拟网卡，因为这里根本没有网卡。&lt;/p&gt;&lt;p&gt;user mode 适合交叉编译后的命令行程序测试、CI 和编译器工作；涉及 kernel driver、boot flow、真实 MMIO 或完整网络 namespace 语义时，应回到 system emulation。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;HMP、QMP 和 Guest Agent 是 3 个平面&lt;a href=&quot;#hmpqmp-和-guest-agent-是-3-个平面&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;HMP：给人使用的 Monitor&lt;a href=&quot;#hmp给人使用的-monitor&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;HMP 是 &lt;code&gt;(qemu)&lt;/code&gt; prompt 后面的交互命令：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(qemu) info status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(qemu) info registers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(qemu) info pci&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(qemu) info block&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(qemu) stop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(qemu) cont&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(qemu) quit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它适合临时观察。输出面向人，会随版本调整，不应让生产脚本靠正则解析。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;QMP：给程序使用的控制 API&lt;a href=&quot;#qmp给程序使用的控制-api&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;QMP 使用以 CRLF 分隔的 JSON object。连接建立后，server 先发送 greeting；client 必须完成 &lt;code&gt;qmp_capabilities&lt;/code&gt; negotiation，随后才能发普通命令。下面是我实际运行的最小 Unix socket client 的核心：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; socket&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;sock &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; socket.&lt;/span&gt;&lt;span&gt;socket&lt;/span&gt;&lt;span&gt;(socket.&lt;/span&gt;&lt;span&gt;AF_UNIX&lt;/span&gt;&lt;span&gt;, socket.&lt;/span&gt;&lt;span&gt;SOCK_STREAM&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;sock.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;/tmp/qmp.sock&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;stream &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; sock.&lt;/span&gt;&lt;span&gt;makefile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;rwb&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;buffering&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;&lt;span&gt;(json.&lt;/span&gt;&lt;span&gt;loads&lt;/span&gt;&lt;span&gt;(stream.&lt;/span&gt;&lt;span&gt;readline&lt;/span&gt;&lt;span&gt;()))  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# greeting&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; request &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;execute&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;qmp_capabilities&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;execute&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;query-status&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;execute&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;query-kvm&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;execute&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;query-cpus-fast&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;execute&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;quit&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;stream.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;((json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;(request) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\r\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; json.&lt;/span&gt;&lt;span&gt;loads&lt;/span&gt;&lt;span&gt;(stream.&lt;/span&gt;&lt;span&gt;readline&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; request[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(response)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;break&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;QEMU 由以下参数启动并停在第一条 Guest 指令之前：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pc,accel=tcg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;64M&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-display&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-serial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-monitor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-qmp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;unix:/tmp/qmp.sock,server=on,wait=off&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;关键响应是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;return&quot;&lt;/span&gt;&lt;span&gt;:{&lt;/span&gt;&lt;span&gt;&quot;status&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&quot;prelaunch&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&quot;running&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;return&quot;&lt;/span&gt;&lt;span&gt;:{&lt;/span&gt;&lt;span&gt;&quot;enabled&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&quot;present&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;present=true&lt;/code&gt; 表示该 QEMU binary 与环境存在 KVM 能力，&lt;code&gt;enabled=false&lt;/code&gt; 则准确反映这台 VM 明确选择了 TCG。&lt;code&gt;-S&lt;/code&gt; 让状态停在 &lt;code&gt;prelaunch&lt;/code&gt;。我喜欢这种接口：没有从彩色日志猜心情，只有结构化状态。&lt;/p&gt;&lt;p&gt;QMP response 的 &lt;code&gt;id&lt;/code&gt; 对应 request；event 则可能异步到来。客户端不能假设读到的下一行一定是刚才命令的 response，必须按 &lt;code&gt;id&lt;/code&gt; 配对，并单独处理 &lt;code&gt;event&lt;/code&gt;。生产代码还要处理 partial read、断连、schema/version 差异和 error object。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Guest Agent：进入 Guest OS 的通道&lt;a href=&quot;#guest-agent进入-guest-os-的通道&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;QEMU Guest Agent 是 Guest 内运行的进程，常通过 virtio-serial 与 Host 管理层通信。它能报告 Guest IP、freeze filesystem、关机或执行受支持操作。QMP 能问 QEMU “vCPU 是否暂停”，却不能凭空知道 Guest 里的 ext4 是否 freeze；QGA 正是跨过这条边界。&lt;/p&gt;&lt;p&gt;不要把 QMP 或 GDB socket 暴露到不可信网络。它们通常没有应用层认证；能控制 VM 或调试 Guest 的接口本质上就是高权限接口。Unix socket 加严格文件权限是本地实验的稳妥默认值。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;用 GDB 在第一条指令前拦住机器&lt;a href=&quot;#用-gdb-在第一条指令前拦住机器&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;QEMU 内置 gdbstub。最短入口是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-kernel&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arch/x86/boot/bzImage&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-append&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;console=ttyS0&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-S&lt;/code&gt;：创建 machine，但不启动 vCPU。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-s&lt;/code&gt;：等价于 &lt;code&gt;-gdb tcp::1234&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;另一个终端连接：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;gdb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;vmlinux&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;gdb&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;remote&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:1234&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;gdb&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;hbreak&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start_kernel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;gdb&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;调内核时应给 GDB &lt;code&gt;vmlinux&lt;/code&gt;，不是压缩后的 &lt;code&gt;bzImage&lt;/code&gt;。&lt;code&gt;vmlinux&lt;/code&gt; 保留 ELF section 与 symbol；QEMU 启动的是可引导压缩镜像，两者承担不同角色。若 KASLR 让 runtime address 与 symbol address 不一致，可以在实验 command line 加 &lt;code&gt;nokaslr&lt;/code&gt;，或者正确处理 relocation，而不是把断点下在希望它出现的地址。&lt;/p&gt;&lt;p&gt;调 16-bit 启动扇区时，连接后可用：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set architecture i8086&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;x/10i $cs*16+$eip&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;info registers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;TCG 下 QEMU 可提供大量 hardware breakpoint/watchpoint；KVM 的能力取决于 accelerator 实现与硬件。user-mode TCG 还不支持 watchpoint。调试 multicore machine 时，每个 vCPU 会作为 GDB thread 暴露，多 cluster machine 还可能需要 &lt;code&gt;target extended-remote&lt;/code&gt; 与多个 inferior。&lt;/p&gt;&lt;p&gt;为避免固定 TCP port 和误暴露，我更喜欢 Unix socket：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-chardev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;socket,path=/tmp/gdb.sock,server=on,wait=off,id=gdb0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-gdb &lt;/span&gt;&lt;span&gt;chardev:gdb0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-S&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;GDB 9.0 及以上可以：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target remote /tmp/gdb.sock&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;日志、trace 和可重复执行&lt;a href=&quot;#日志trace-和可重复执行&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;串口日志、QEMU debug log 与 trace event 是 3 类不同证据：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-serial file:guest.log&lt;/code&gt; 记录 Guest 经 UART 输出的内容。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d ... -D qemu.log&lt;/code&gt; 记录 QEMU 的执行或设备 debug 信息。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-trace ...&lt;/code&gt; 订阅代码中定义的 trace event。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;先查询支持项：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-trace&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;调早期启动常见写法：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;guest_errors,int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-D&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;qemu.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-d in_asm&lt;/code&gt; 可以看到翻译的 Guest instruction block，但完整 Linux boot 会快速生成巨大日志。先缩小复现、使用 &lt;code&gt;-S&lt;/code&gt; 和 breakpoint，或只在目标阶段打开日志。把几 GiB &lt;code&gt;in_asm&lt;/code&gt; 当作理解系统的方法，很像为了找一根针而给整个草垛做逐原子 CT，技术上努力，方向上稍微有点可疑。&lt;/p&gt;&lt;p&gt;需要可重放的 TCG 实验时，QEMU 还有 instruction counting 与 record/replay 机制，但它们会约束设备、时钟和加速器。&lt;code&gt;-icount&lt;/code&gt; 的虚拟时间不等于真实 wall-clock 性能；不要把确定性工具产生的时间数字拿去做 VM 性能榜。&lt;/p&gt;&lt;p&gt;更深入的动态分析可以使用 TCG plugin，按 instruction、basic block 或 memory access 订阅事件。plugin 能观察非常细的执行，却不应被理解成可以随意修改 Guest state 的通用 hook。先用 QMP、GDB 和现成 trace event，把 plugin 留给确实需要指令级统计的问题。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;时钟、SMP 和”偶尔失败”&lt;a href=&quot;#时钟smp-和偶尔失败&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;VM 里至少同时存在几种时间：Host wall clock、QEMU virtual clock、Guest wall clock、Guest monotonic clock，以及设备 timer。暂停 VM、Host 休眠、过载或迁移都会让它们以不同方式变化。&lt;/p&gt;&lt;p&gt;&lt;code&gt;-smp&lt;/code&gt; 也不仅是一个数量：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-smp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cpus=4,sockets=1,cores=2,threads=&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Guest 会看见拓扑，调度器与软件许可可能据此作决定。若只写 &lt;code&gt;-smp 4&lt;/code&gt;，QEMU 会根据 machine 默认规则补齐 topology；需要可复现实验时应把真正相关的拓扑写全。&lt;/p&gt;&lt;p&gt;并发 bug 在 TCG 单线程、TCG 多线程和 KVM 下可能表现不同。TCG 没复现不等于真实硬件没有 bug，KVM 复现也不等于 QEMU 设备模型必然有错。先确定问题落在 Guest 软件、虚拟 CPU、设备 contract 还是 Host backend，再更换加速器做交叉验证。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一套可重复的内核实验命令&lt;a href=&quot;#一套可重复的内核实验命令&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我现在不会从一条不断追加参数的历史命令开始，而是明确命名每一层：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pocket-kernel&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pc-q35-10.2,accel=tcg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-cpu&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;256M&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-smp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cpus=1,sockets=1,cores=1,threads=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-nodefaults&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-no-reboot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-display&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-chardev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stdio,id=serial0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-device&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;isa-serial,chardev=serial0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-monitor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-qmp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;unix:/tmp/pocket-kernel.qmp,server=on,wait=off&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-chardev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;socket,path=/tmp/pocket-kernel.gdb,server=on,wait=off,id=gdb0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-gdb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chardev:gdb0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-kernel&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arch/x86/boot/bzImage&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-initrd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;initramfs.cpio&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-append&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;console=ttyS0 rdinit=/init panic=-1 nokaslr&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-nodefaults&lt;/code&gt; 移除了常见默认外设，所以我显式创建 &lt;code&gt;isa-serial&lt;/code&gt;，再通过 ID 为 &lt;code&gt;serial0&lt;/code&gt; 的 chardev 接到 stdio。最小化的目标是让依赖可见，不是参加参数最短比赛。&lt;/p&gt;&lt;p&gt;用于网络和持久 rootfs 时再加：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-blockdev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;driver=file,node-name=root-file,filename=root.qcow2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-blockdev &lt;/span&gt;&lt;span&gt;driver=qcow2,node-name=root,read-only=off,file=root-file&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-device &lt;/span&gt;&lt;span&gt;virtio-blk-pci,drive=root&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-netdev &lt;/span&gt;&lt;span&gt;user,id=net0,hostfwd=tcp:127.0.0.1:2222-:22&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-device &lt;/span&gt;&lt;span&gt;virtio-net-pci,netdev=net0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;对应内核至少要检查：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_SERIAL_8250=y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_SERIAL_8250_CONSOLE=y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_VIRTIO=y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_VIRTIO_PCI=y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_VIRTIO_BLK=y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_VIRTIO_NET=y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;具体符号仍以目标 Linux 版本和 machine 为准。root 设备还要在 &lt;code&gt;-append&lt;/code&gt; 中给出正确 &lt;code&gt;root=/dev/vda...&lt;/code&gt;、filesystem driver 和等待策略。看到 &lt;code&gt;VFS: Unable to mount root fs&lt;/code&gt; 时，我会先核对 4 件事：Guest 设备类型、transport driver、block/filesystem driver 是否内建、root path 是否与实际分区一致。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见失败怎样沿层次定位？&lt;a href=&quot;#常见失败怎样沿层次定位&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;QEMU 启动前就报 property 或 bus 错误&lt;a href=&quot;#qemu-启动前就报-property-或-bus-错误&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是 Host 侧 machine graph 没建起来。检查 &lt;code&gt;-device TYPE,help&lt;/code&gt;、ID 拼写、bus 名、machine 是否提供该 bus，以及当前 QEMU build 是否包含设备。Guest kernel 尚未执行，不要先改 Kconfig。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;屏幕全黑或终端没有日志&lt;a href=&quot;#屏幕全黑或终端没有日志&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;分别检查显示与 console：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;-display none&lt;/code&gt; 是否把唯一图形输出关闭。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-serial stdio&lt;/code&gt; 或 &lt;code&gt;-nographic&lt;/code&gt; 是否连接正确 UART。&lt;/li&gt;
&lt;li&gt;kernel command line 是否使用 &lt;code&gt;ttyS0&lt;/code&gt;、&lt;code&gt;ttyAMA0&lt;/code&gt; 等正确设备。&lt;/li&gt;
&lt;li&gt;UART driver 和 console support 是否内建。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;-S&lt;/code&gt; 时是否忘了从 GDB 或 QMP &lt;code&gt;cont&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;No bootable device&lt;/code&gt;&lt;a href=&quot;#no-bootable-device&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;固件没有找到可启动介质。检查镜像内容、boot signature/EFI executable、&lt;code&gt;bootindex&lt;/code&gt;、磁盘 interface 与 firmware 能否驱动它。一个包含 ext4 的文件不自动等于含有 bootloader 的 boot disk。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;内核看不见磁盘或网卡&lt;a href=&quot;#内核看不见磁盘或网卡&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;先用 QMP/HMP 确认 device 真的存在并连接 backend，再看 Guest &lt;code&gt;dmesg&lt;/code&gt;、PCI/Device Tree 枚举和驱动配置。VirtIO PCI 与 VirtIO MMIO 需要不同 transport；设备存在但没有 driver 与设备根本没创建，会得到相似的”看不见”，修复层却完全不同。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;Could not access KVM kernel module&lt;/code&gt;&lt;a href=&quot;#could-not-access-kvm-kernel-module&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;检查 &lt;code&gt;/dev/kvm&lt;/code&gt;、Host CPU virtualization、内核模块、权限和容器 device passthrough。需要兼容 fallback 时可写：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-accel&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;kvm:tcg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;但性能实验不应静默 fallback。用 QMP &lt;code&gt;query-kvm&lt;/code&gt; 记录 &lt;code&gt;enabled&lt;/code&gt;，否则以为自己测了 KVM，实际跑了一夜 TCG，也算一种很昂贵的类型错误。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;SSH 端口转发连不上&lt;a href=&quot;#ssh-端口转发连不上&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;检查 Host port 是否被占用、&lt;code&gt;hostfwd&lt;/code&gt; 的绑定地址、Guest sshd 是否监听、Guest firewall 和地址配置。QEMU user networking 默认不接受任意外部入站，端口转发是明确开的洞。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;镜像提示 format probing 警告&lt;a href=&quot;#镜像提示-format-probing-警告&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;显式写 &lt;code&gt;format=raw&lt;/code&gt; 或 &lt;code&gt;format=qcow2&lt;/code&gt;。格式自动探测不仅让行为含糊，对 raw 镜像还可能因安全限制阻止对第 0 block 的写入。命令行里知道格式，就把它写出来。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;安全边界不能靠”它只是虚拟机”&lt;a href=&quot;#安全边界不能靠它只是虚拟机&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我会保留这些默认纪律：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;用普通用户运行 QEMU，不给整个进程 root。&lt;/li&gt;
&lt;li&gt;不可信 Guest 只配置需要的设备与 backend。&lt;/li&gt;
&lt;li&gt;QMP、HMP、GDB 和 migration socket 使用 Unix socket 或绑定 loopback，并限制权限。&lt;/li&gt;
&lt;li&gt;不随意添加 Host 目录共享、USB passthrough、VFIO、&lt;code&gt;/dev/mem&lt;/code&gt; 或可写物理块设备。&lt;/li&gt;
&lt;li&gt;user networking 的 &lt;code&gt;hostfwd&lt;/code&gt; 显式绑定 &lt;code&gt;127.0.0.1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;对 base image 只读，实验写入 overlay。&lt;/li&gt;
&lt;li&gt;semihosting 只给可信裸机代码使用，因为它可以绕过 Guest/Host 隔离访问 Host 资源。&lt;/li&gt;
&lt;li&gt;记录 QEMU version、machine version、CPU model、firmware、镜像 hash 和完整参数。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;VFIO passthrough 能减少设备模拟并接近原生性能，但它把 IOMMU group、DMA 隔离、reset semantics 和 Host driver ownership 带进系统。它不是”再加一个 &lt;code&gt;-device&lt;/code&gt;”级别的学习步骤。我会先把纯模拟设备与 VirtIO 路径弄清，再碰 passthrough。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我最终保留的心智模型&lt;a href=&quot;#我最终保留的心智模型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;现在我把 QEMU 看成 5 张互相连接的图：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;执行图&lt;/strong&gt;：Guest ISA 经 TCG 或 KVM 前进。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;machine 图&lt;/strong&gt;：CPU、RAM、bus、中断控制器和设备占据确定位置。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I/O 图&lt;/strong&gt;：Guest device 经 block、network、character backend 连接 Host。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;启动图&lt;/strong&gt;：reset 后进入 firmware、bootloader 或 direct kernel boot。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;控制图&lt;/strong&gt;：人用 HMP，程序用 QMP，调试器用 gdbstub，Guest Agent 报告 OS 内部状态。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;512 字节启动扇区只用了 1 个 vCPU、1 个 UART、1 个软盘和 1 个测试退出设备，却已经证明了这 5 张图如何接起来。随后 992 字节 AArch64 ELF 又拿掉整台 machine，只留下指令翻译与 syscall translation。最后，&lt;code&gt;virt&lt;/code&gt; 的 1 MiB DTB 把 ARM machine 的 RAM、PL011、GIC、PCI 和 32 个 VirtIO MMIO slot 全部摊在文本里。QEMU 没有变简单，但黑盒变成了几张可以逐边检查的图。&lt;/p&gt;&lt;p&gt;接下来我最想做的是把上一篇 &lt;code&gt;pocket_sensor&lt;/code&gt; 从一个靠 &lt;code&gt;insmod&lt;/code&gt; 自说自话的模块，改成真正的 platform driver：在 ARM &lt;code&gt;virt&lt;/code&gt; 上增加一个小型 MMIO 设备模型，让 QEMU 生成对应 Device Tree 节点，再观察 Linux 从 &lt;code&gt;compatible&lt;/code&gt; 匹配到 &lt;code&gt;probe()&lt;/code&gt;、中断与 &lt;code&gt;remove()&lt;/code&gt;。到那一步，Kconfig、Kbuild、Device Tree、driver core 和 QEMU device model 会在同一条可执行链上相遇。&lt;/p&gt;&lt;p&gt;很好。我终于不再把 &lt;code&gt;-nographic&lt;/code&gt; 当作启动虚拟机的咒语了。它只是把一根虚拟串口线接到了我的终端，剩下 455 个设备还在后面安静地等着制造新问题。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gitlab.com/qemu-project/qemu/-/blob/v10.2.4/docs/system/introduction.rst&quot; target=&quot;_blank&quot;&gt;QEMU 10.2.4: System Emulation Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.qemu.org/docs/master/system/invocation.html&quot; target=&quot;_blank&quot;&gt;QEMU: System Invocation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.qemu.org/docs/master/system/images.html&quot; target=&quot;_blank&quot;&gt;QEMU: Disk Images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.qemu.org/docs/master/system/devices/net.html&quot; target=&quot;_blank&quot;&gt;QEMU: Network Emulation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.qemu.org/docs/master/user/main.html&quot; target=&quot;_blank&quot;&gt;QEMU: User Space Emulator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.qemu.org/docs/master/system/gdb.html&quot; target=&quot;_blank&quot;&gt;QEMU: GDB Usage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.qemu.org/docs/master/interop/qmp-spec.html&quot; target=&quot;_blank&quot;&gt;QEMU Machine Protocol Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.qemu.org/docs/master/interop/qemu-qmp-ref.html&quot; target=&quot;_blank&quot;&gt;QEMU Machine Protocol Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.qemu.org/docs/master/system/target-arm.html&quot; target=&quot;_blank&quot;&gt;QEMU: Arm System Emulator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gitlab.com/qemu-project/qemu/-/blob/v10.2.4/hw/misc/debugexit.c&quot; target=&quot;_blank&quot;&gt;QEMU 10.2.4: isa-debug-exit implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oasis-open.org/virtio/virtio/v1.3/virtio-v1.3.html&quot; target=&quot;_blank&quot;&gt;OASIS VirtIO 1.3 Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.u-boot.org/en/v2026.07/board/emulation/qemu-arm.html&quot; target=&quot;_blank&quot;&gt;U-Boot v2026.07: QEMU ARM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded></item><item><title>我追了一遍 Kbuild: 从一行 obj-m 到可以加载的 Linux 内核模块</title><link>https://bfmhno3.github.io/posts/understanding-kbuild/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/understanding-kbuild/</guid><description>我用 Linux 6.18 写了一个双文件外部模块，追踪 obj-m、scripts/Makefile.build、.cmd、modpost 和最终 .ko，再把它装进 QEMU 并迁移回内核树验证 n、m、y。</description><pubDate>Fri, 04 Sep 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;/posts/understanding-kconfig/&quot;&gt;上一篇文章&lt;/a&gt;停在一行很小的 Makefile 上：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;CONFIG_POCKET_SENSOR&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; pocket_sensor.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Kconfig 已经算出 &lt;code&gt;CONFIG_POCKET_SENSOR=m&lt;/code&gt;，但这一行怎么变成一个能让内核执行的 &lt;code&gt;.ko&lt;/code&gt;？我从 Linux v6.18 拉出这根线，写了一个双文件模块，编译它，拆开中间产物，再把它塞进 QEMU 里的 Linux 6.18。最后我真的看到了 &lt;code&gt;module_init()&lt;/code&gt; 和 &lt;code&gt;module_exit()&lt;/code&gt; 各执行一次。很好，配电柜之后，我们终于开始追电线了。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Kconfig&lt;br /&gt;&lt;br /&gt;CONFIG_POCKET_SENSOR=m&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;auto.conf 与 obj-m&lt;br /&gt;&lt;br /&gt;选择模块路径&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Makefile.build&lt;br /&gt;&lt;br /&gt;编译 core.o 和 stats.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ld -r&lt;br /&gt;&lt;br /&gt;合成 pocket_sensor.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;modpost 与 modfinal&lt;br /&gt;&lt;br /&gt;生成 pocket_sensor.ko&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;finit_module()&lt;br /&gt;&lt;br /&gt;执行 pocket_sensor_init()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Kconfig&lt;br /&gt;&lt;br /&gt;CONFIG_POCKET_SENSOR=m&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;auto.conf 与 obj-m&lt;br /&gt;&lt;br /&gt;选择模块路径&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Makefile.build&lt;br /&gt;&lt;br /&gt;编译 core.o 和 stats.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ld -r&lt;br /&gt;&lt;br /&gt;合成 pocket_sensor.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;modpost 与 modfinal&lt;br /&gt;&lt;br /&gt;生成 pocket_sensor.ko&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;finit_module()&lt;br /&gt;&lt;br /&gt;执行 pocket_sensor_init()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;本文只讨论 Linux Kbuild。U-Boot 也继承了 Kbuild 的许多写法，但它没有 Linux 这条可加载模块链，最终镜像路径也不同。这里不把两种系统勉强揉成一种。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先把实验钉在具体版本上&lt;a href=&quot;#先把实验钉在具体版本上&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我的实验环境如下：&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;项目&lt;/th&gt;&lt;th&gt;实验值&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Linux 源码&lt;/td&gt;&lt;td&gt;&lt;code&gt;v6.18&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;架构&lt;/td&gt;&lt;td&gt;&lt;code&gt;x86_64&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GNU Make&lt;/td&gt;&lt;td&gt;&lt;code&gt;4.4.1&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GCC&lt;/td&gt;&lt;td&gt;&lt;code&gt;15.2.0&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GNU Binutils&lt;/td&gt;&lt;td&gt;&lt;code&gt;2.46&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;kmod&lt;/td&gt;&lt;td&gt;&lt;code&gt;31&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;QEMU&lt;/td&gt;&lt;td&gt;&lt;code&gt;10.2.4&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;BusyBox&lt;/td&gt;&lt;td&gt;&lt;code&gt;1.37.0&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;内核和外部模块统一使用 GCC 15.2.0 与 GNU Binutils 2.46。正式构建也应复用目标内核的工具链、配置和构建目录，因为 Linux 不向外部模块提供稳定的内核内 ABI。&lt;/p&gt;&lt;p&gt;我用下面的源码固定分析基准：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--depth&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--branch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v6.18&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;https://github.com/torvalds/linux.git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;linux-6.18&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Linux 6.18 是这里的机制快照。后续版本可能移动规则、拆分生成文件或改变命令行，所以文中的源码链接都锁在 &lt;code&gt;v6.18&lt;/code&gt; 标签。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;先做出一个真的 &lt;code&gt;.ko&lt;/code&gt;&lt;a href=&quot;#先做出一个真的-ko&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;时机&lt;/th&gt;&lt;th&gt;可观察行为&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;加载&lt;/td&gt;&lt;td&gt;读取 &lt;code&gt;label&lt;/code&gt; 和 &lt;code&gt;samples&lt;/code&gt; 两个模块参数&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;运行&lt;/td&gt;&lt;td&gt;&lt;code&gt;stats.c&lt;/code&gt; 分配并初始化一小段样本数组&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;卸载&lt;/td&gt;&lt;td&gt;释放数组并打印样本数&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;目录只有 5 个输入文件：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket-sensor/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── Kbuild&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── Makefile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── core.c&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── stats.c&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└── pocket_sensor.h&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;core.c&lt;/code&gt; 负责模块生命周期和参数：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;GPL-2.0&lt;/span&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;linux/init.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;linux/module.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;pocket_sensor.h&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;label &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;desk&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;module_param&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; charp&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;444&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MODULE_PARM_DESC&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;Sensor label&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;unsigned&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; samples &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;module_param&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;samples&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;uint&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;444&lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MODULE_PARM_DESC&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;samples&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;Number of synthetic samples&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; __init &lt;/span&gt;&lt;span&gt;pocket_sensor_init&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; ret;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ret &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pocket_stats_start&lt;/span&gt;&lt;span&gt;(samples);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (ret)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; ret;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;pr_info&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;pocket_sensor: loaded label=&lt;/span&gt;&lt;span&gt;%s&lt;/span&gt;&lt;span&gt; samples=&lt;/span&gt;&lt;span&gt;%u&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;label, samples);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; __exit &lt;/span&gt;&lt;span&gt;pocket_sensor_exit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;pr_info&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;pocket_sensor: unloaded samples=&lt;/span&gt;&lt;span&gt;%u&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;pocket_stats_stop&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;module_init&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pocket_sensor_init&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;module_exit&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pocket_sensor_exit&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MODULE_AUTHOR&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;bfmhno3&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MODULE_DESCRIPTION&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Small Kbuild experiment&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MODULE_LICENSE&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;GPL&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;stats.c&lt;/code&gt; 提供第二个编译单元：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;GPL-2.0&lt;/span&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;linux/errno.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;linux/slab.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;linux/types.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;pocket_sensor.h&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; u32 &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;values;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;unsigned&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; value_count;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pocket_stats_start&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;unsigned&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;unsigned&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; i;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;count &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; count &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4096&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;EINVAL;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;values &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;kcalloc&lt;/span&gt;&lt;span&gt;(count, &lt;/span&gt;&lt;span&gt;sizeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;values), GFP_KERNEL);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;values)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;ENOMEM;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; count; i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;[i] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; i;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;value_count &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; count;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;unsigned&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pocket_stats_stop&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;unsigned&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; count &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; value_count;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;kfree&lt;/span&gt;&lt;span&gt;(values);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;values &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;NULL&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;value_count &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; count;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;头文件只是两个编译单元之间的接口：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;GPL-2.0&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#ifndef&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;POCKET_SENSOR_H&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#define&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;POCKET_SENSOR_H&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pocket_stats_start&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;unsigned&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;unsigned&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pocket_stats_stop&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;真正交给 Kbuild 的文件只有两行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-m&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; pocket_sensor.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor-y&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; core.o stats.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;第一行声明最终模块，第二行声明组成这个模块的对象。&lt;code&gt;pocket_sensor-y&lt;/code&gt; 里的 &lt;code&gt;y&lt;/code&gt; 不是说整个模块内建；它表示这些对象无条件组成 &lt;code&gt;pocket_sensor.o&lt;/code&gt;。最终走内建还是模块路径，仍由外层的 &lt;code&gt;obj-y&lt;/code&gt; 或 &lt;code&gt;obj-m&lt;/code&gt; 决定。&lt;/p&gt;&lt;p&gt;为了让目录里直接执行 &lt;code&gt;make&lt;/code&gt;，我另外放一个很薄的包装 Makefile：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;KDIR&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;?=&lt;/span&gt;&lt;span&gt; /path/to/linux-6.18-build&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.PHONY&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; all clean&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;MAKE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; -C &lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;KDIR&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; M=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;CURDIR&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; modules&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;clean&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;MAKE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; -C &lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;KDIR&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; M=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;CURDIR&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; clean&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;KDIR&lt;/code&gt; 指向已经配置并完成构建的 Linux 6.18 输出目录。只运行 &lt;code&gt;modules_prepare&lt;/code&gt; 可以补齐很多生成头和脚本，但在 &lt;code&gt;CONFIG_MODVERSIONS=y&lt;/code&gt; 时，它不会生成 &lt;code&gt;Module.symvers&lt;/code&gt;。我这次完整构建了实验内核和 &lt;code&gt;modules&lt;/code&gt; 目标，避免把缺失符号版本伪装成模块问题。&lt;/p&gt;&lt;p&gt;第一次构建输出是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$ make&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CC [M]  core.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CC [M]  stats.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;LD [M]  pocket_sensor.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;MODPOST Module.symvers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CC [M]  pocket_sensor.mod.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CC [M]  .module-common.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;LD [M]  pocket_sensor.ko&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;干净构建耗时 &lt;code&gt;1.78&lt;/code&gt; 秒，立刻再构建一次耗时 &lt;code&gt;0.89&lt;/code&gt; 秒，第二次没有执行任何 &lt;code&gt;CC&lt;/code&gt;、&lt;code&gt;LD&lt;/code&gt; 或 &lt;code&gt;MODPOST&lt;/code&gt;。这里的绝对时间包含启动多层 Make 的固定成本，不适合拿去跑编译器排行榜；有趣的是工作量从 7 个构建动作降到了 0。&lt;/p&gt;&lt;p&gt;5 个输入文件旁边出现了 19 个生成文件：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;core.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;stats.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor.mod&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;modules.order&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Module.symvers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor.mod.c&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor.mod.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.module-common.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor.ko&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.core.o.cmd&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.stats.o.cmd&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.pocket_sensor.mod.cmd&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.pocket_sensor.o.cmd&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.pocket_sensor.mod.o.cmd&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.pocket_sensor.ko.cmd&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;此外还有为 &lt;code&gt;modules.order&lt;/code&gt;、&lt;code&gt;Module.symvers&lt;/code&gt; 和 &lt;code&gt;.module-common.o&lt;/code&gt; 保存命令的 3 个隐藏 &lt;code&gt;.cmd&lt;/code&gt; 文件。连同上面的 16 项，总数是 19。Kbuild 很像一条留下完整货运单的装配线：&lt;code&gt;.ko&lt;/code&gt; 是箱子，旁边这些小文件告诉它箱子为什么需要重新装。&lt;/p&gt;&lt;p&gt;成品大小为 &lt;code&gt;6944&lt;/code&gt; 字节：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;core.o               4064 bytes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;stats.o              1608 bytes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor.o      5136 bytes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor.mod.o  2448 bytes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor.ko     6944 bytes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我还编译了一个只有单个 &lt;code&gt;.c&lt;/code&gt; 的最小模块。它生成 &lt;code&gt;4552&lt;/code&gt; 字节的 &lt;code&gt;.ko&lt;/code&gt; 和 15 个派生文件；双文件实验生成 19 个派生文件。两个模块行为不同，所以 &lt;code&gt;2392&lt;/code&gt; 字节的体积差不能全算作”多文件税”。真正由复合模块新增的关键动作，是把两个局部 &lt;code&gt;.o&lt;/code&gt; 用 &lt;code&gt;ld -r&lt;/code&gt; 合成最终的 &lt;code&gt;pocket_sensor.o&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;make -C ... M=... modules&lt;/code&gt; 为什么像走了两遍？&lt;a href=&quot;#make--c--m-modules-为什么像走了两遍&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;命令是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-C&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$KDIR&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;M=&quot;&lt;/span&gt;&lt;span&gt;$PWD&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;modules&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;部分&lt;/th&gt;&lt;th&gt;作用&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-C &quot;$KDIR&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;进入目标内核构建目录；这里必须有 &lt;code&gt;.config&lt;/code&gt;、生成头、构建工具和 &lt;code&gt;Module.symvers&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;M=&quot;$PWD&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;声明外部模块，并给出源码目录的绝对路径&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;modules&lt;/code&gt;&lt;/td&gt;&lt;td&gt;选择模块构建目标；它也是外部模块的默认目标&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;外层 Makefile&lt;/td&gt;&lt;td&gt;把调用送进内核 Kbuild，不自行拼接内核编译参数&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;如果把包装逻辑和 Kbuild 声明写在同一个 Makefile，传统写法会检查 &lt;code&gt;KERNELRELEASE&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ifneq&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;KERNELRELEASE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-m&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; pocket_sensor.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor-y&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; core.o stats.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;KDIR&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;?=&lt;/span&gt;&lt;span&gt; /path/to/linux-6.18-build&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;MAKE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; -C &lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;KDIR&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; M=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;CURDIR&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; modules&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;第一次读取时，普通 Make 看见 &lt;code&gt;all&lt;/code&gt; 并调用内核。第二次进入 Kbuild 后，&lt;code&gt;KERNELRELEASE&lt;/code&gt; 已经存在，于是同一文件只暴露 &lt;code&gt;obj-m&lt;/code&gt;。我选择独立的 &lt;code&gt;Kbuild&lt;/code&gt; 文件后，不再需要这个分支；Kbuild 会优先读取 &lt;code&gt;Kbuild&lt;/code&gt;，找不到才读取 &lt;code&gt;Makefile&lt;/code&gt;。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;内核 Kbuild外层 Makefile用户内核 Kbuild外层 Makefile用户makemake -C KDIR M=CURDIR modules设置 KERNELRELEASE读取 Kbuild 或 Makefile 的内核部分生成 pocket_sensor.ko
&lt;/div&gt;&lt;div&gt;内核 Kbuild外层 Makefile用户内核 Kbuild外层 Makefile用户makemake -C KDIR M=CURDIR modules设置 KERNELRELEASE读取 Kbuild 或 Makefile 的内核部分生成 pocket_sensor.ko
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Linux 6.13 以后还支持 &lt;code&gt;-f&lt;/code&gt; 入口，它不要求先切换工作目录：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$KDIR&lt;/span&gt;&lt;span&gt;/Makefile&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;M=&quot;&lt;/span&gt;&lt;span&gt;$PWD&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;modules&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;两种入口最终进入同一套规则。这里的”两遍”不是 GNU Make 神秘地重复编译，而是包装层和内核构建层各读一次自己负责的描述。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;src&lt;/code&gt;、&lt;code&gt;obj&lt;/code&gt;、&lt;code&gt;srctree&lt;/code&gt;、&lt;code&gt;objtree&lt;/code&gt;&lt;a href=&quot;#srcobjsrctreeobjtree&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;变量&lt;/th&gt;&lt;th&gt;指向&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;srctree&lt;/code&gt;&lt;/td&gt;&lt;td&gt;内核源码树根目录&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;objtree&lt;/code&gt;&lt;/td&gt;&lt;td&gt;内核输出树根目录；非分离构建时通常等于 &lt;code&gt;srctree&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;src&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当前 Kbuild 目录的源码路径&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;obj&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当前 Kbuild 目录的输出路径或处理位置&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;构建内核时，&lt;code&gt;O=/path/to/out&lt;/code&gt; 选择 &lt;code&gt;objtree&lt;/code&gt;。它不等于”把外部模块输出放到这里”。Linux 6.18 为外部模块单独提供 &lt;code&gt;MO=/path/to/module-out&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-C&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$KDIR&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;M=&quot;&lt;/span&gt;&lt;span&gt;$PWD&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MO=&quot;&lt;/span&gt;&lt;span&gt;$PWD&lt;/span&gt;&lt;span&gt;/out&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;modules&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;所以我会把边界记成：&lt;code&gt;O=&lt;/code&gt; 管内核输出树，&lt;code&gt;M=&lt;/code&gt; 指外部模块源码，&lt;code&gt;MO=&lt;/code&gt; 可选地管外部模块输出。3 个路径混在一起时，很多看似缺头文件的问题其实只是拿错了构建目录。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一行 &lt;code&gt;obj-m&lt;/code&gt; 怎样进入 &lt;code&gt;scripts/Makefile.build&lt;/code&gt;？&lt;a href=&quot;#一行-obj-m-怎样进入-scriptsmakefilebuild&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;上一篇文章已经追到这个入口：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;build&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; -f &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;srctree&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/scripts/Makefile.build obj&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;内核随后用近似下面的子 Make 调用处理一个目录：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scripts/Makefile.build&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;obj=&amp;lt;current-directory&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Linux 6.18 的 &lt;code&gt;scripts/Makefile.build&lt;/code&gt; 先清空 &lt;code&gt;obj-y&lt;/code&gt;、&lt;code&gt;obj-m&lt;/code&gt;、flags 等局部变量，再按顺序读入配置和规则：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Makefile.build&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;include/config/auto.conf&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Kbuild.include&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Makefile.compiler&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;当前目录 Kbuild 或 Makefile&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Makefile.lib&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;展开 obj-y、obj-m 和复合对象&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Makefile.build&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;include/config/auto.conf&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Kbuild.include&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Makefile.compiler&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;当前目录 Kbuild 或 Makefile&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Makefile.lib&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;展开 obj-y、obj-m 和复合对象&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这解释了为什么一个目录中的 Kbuild 可以非常短。公共文件已经定义了如何编译 &lt;code&gt;.c&lt;/code&gt;、怎样保存命令、如何归档 &lt;code&gt;built-in.a&lt;/code&gt;、怎样生成 &lt;code&gt;modules.order&lt;/code&gt;。目录文件只声明输入图，不重复写编译器菜谱。&lt;/p&gt;&lt;p&gt;对这两行输入：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-m&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; pocket_sensor.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor-y&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; core.o stats.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Makefile.build&lt;/code&gt; 算出几组直接影响实验的集合：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-m       = pocket_sensor.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;multi-obj-m = pocket_sensor.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;real-obj-m  = core.o stats.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;multi-obj-m&lt;/code&gt; 是需要部分链接的复合模块。&lt;code&gt;real-obj-m&lt;/code&gt; 是真正从源码生成的叶子对象。&lt;code&gt;scripts/Makefile.lib&lt;/code&gt; 再通过 &lt;code&gt;part-of-module&lt;/code&gt; 给叶子编译追加 &lt;code&gt;-DMODULE&lt;/code&gt;、&lt;code&gt;KBUILD_MODNAME&lt;/code&gt; 等 flags。我的 &lt;code&gt;V=1&lt;/code&gt; 输出中，&lt;code&gt;core.c&lt;/code&gt; 的命令确实带有：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-DMODULE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-DKBUILD_BASENAME=&apos;&quot;core&quot;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-DKBUILD_MODNAME=&apos;&quot;pocket_sensor&quot;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Kbuild 先生成一个内容只有两行的响应文件 &lt;code&gt;pocket_sensor.mod&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;./core.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;./stats.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;然后执行实质上的：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ld&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-r&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pocket_sensor.o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pocket_sensor.mod&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-r&lt;/code&gt; 产生仍可重定位的合并对象，不把它链接成用户空间可执行文件。此时 &lt;code&gt;core.o&lt;/code&gt; 对 &lt;code&gt;pocket_stats_start()&lt;/code&gt; 的未解析引用由同一次部分链接中的 &lt;code&gt;stats.o&lt;/code&gt; 满足。这个阶段像把两个零件焊成一个仍带接口的总成，内核符号的插头还留在外面，稍后加载器再接。&lt;/p&gt;&lt;p&gt;&lt;code&gt;obj-y&lt;/code&gt; 和 &lt;code&gt;obj-m&lt;/code&gt; 也可能错误地同时列出同一个对象。Linux 6.18 明确执行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-m&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;filter-out&lt;/span&gt;&lt;span&gt; $(&lt;/span&gt;&lt;span&gt;obj-y&lt;/span&gt;&lt;span&gt;),$(&lt;/span&gt;&lt;span&gt;obj-m&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;也就是内建版本优先，模块副本被移除。Kbuild 不会为同一个目标同时制造一份内建对象和一份 &lt;code&gt;.ko&lt;/code&gt;，然后让我们猜哪份算数。&lt;/p&gt;&lt;p&gt;完整的模块前半段现在变成：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;core.c&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;core.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;stats.c&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;stats.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ld -r&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;modpost&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;core.c&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;core.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;stats.c&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;stats.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ld -r&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;modpost&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;.cmd&lt;/code&gt; 才是增量构建的记忆&lt;a href=&quot;#cmd-才是增量构建的记忆&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;判断&lt;/th&gt;&lt;th&gt;Kbuild 使用的依据&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;编译命令是否改变&lt;/td&gt;&lt;td&gt;&lt;code&gt;.cmd&lt;/code&gt; 中保存的上一次完整命令&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;输入是否改变&lt;/td&gt;&lt;td&gt;源文件、头文件和被引用的 &lt;code&gt;CONFIG_*&lt;/code&gt; 标记依赖&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;code&gt;core.o&lt;/code&gt; 构建后，&lt;code&gt;.core.o.cmd&lt;/code&gt; 开头保存完整命令：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;savedcmd_core.o&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; gcc ... -DMODULE ... -c -o core.o core.c&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;source_core.o&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; core.c&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;后面是一长串真实依赖。例如 &lt;code&gt;core.c&lt;/code&gt; 包含的 &lt;code&gt;linux/module.h&lt;/code&gt; 使用了模块开关，因此文件中出现：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;wildcard&lt;/span&gt;&lt;span&gt; include/config/MODULES)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;wildcard&lt;/span&gt;&lt;span&gt; include/config/MODULE_UNLOAD)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;规则链是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;否&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;是&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;if_changed_dep&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;目标、依赖或命令变化？&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;跳过编译&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;cmd_and_fixdep&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;执行 GCC 编译命令&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;生成临时 .d 依赖文件&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/basic/fixdep&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;写入 .core.o.cmd&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;删除临时 .d&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;否&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;是&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;if_changed_dep&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;目标、依赖或命令变化？&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;跳过编译&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;cmd_and_fixdep&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;执行 GCC 编译命令&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;生成临时 .d 依赖文件&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/basic/fixdep&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;写入 .core.o.cmd&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;删除临时 .d&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;if_changed_dep&lt;/code&gt; 会在目标缺失、普通依赖更新或保存的命令变化时重建。&lt;code&gt;fixdep&lt;/code&gt; 还会扫描依赖文件里出现的 &lt;code&gt;CONFIG_FOO&lt;/code&gt;，把它转成 &lt;code&gt;include/config/FOO&lt;/code&gt; 标记依赖。它并不完整解析 C；Linux 6.18 的源码注释甚至直说，注释中出现的 &lt;code&gt;CONFIG_FOO&lt;/code&gt; 也会被抓到。多编一次可以接受，漏编一次不行。&lt;/p&gt;&lt;p&gt;我实际做了 4 轮增量实验：&lt;/p&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;操作&lt;/th&gt;&lt;th&gt;重新执行的主要步骤&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;不改任何文件&lt;/td&gt;&lt;td&gt;无 &lt;code&gt;CC&lt;/code&gt;、&lt;code&gt;LD&lt;/code&gt;、&lt;code&gt;MODPOST&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;只改 &lt;code&gt;core.c&lt;/code&gt; 中的普通注释&lt;/td&gt;&lt;td&gt;&lt;code&gt;core.o&lt;/code&gt;、复合链接、&lt;code&gt;modpost&lt;/code&gt;、最终链接&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;修改编译 flags&lt;/td&gt;&lt;td&gt;&lt;code&gt;core.o&lt;/code&gt; 和 &lt;code&gt;stats.o&lt;/code&gt;、复合链接、&lt;code&gt;modpost&lt;/code&gt;、最终链接&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;修改两者共同包含的 &lt;code&gt;pocket_sensor.h&lt;/code&gt;&lt;/td&gt;&lt;td&gt;两个叶子对象及后续链全部更新&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;配置依赖不需要额外造一个与模块无关的开关。后面的树内迁移直接用 &lt;code&gt;CONFIG_POCKET_SENSOR&lt;/code&gt; 和 &lt;code&gt;CONFIG_POCKET_SENSOR_STATS&lt;/code&gt; 验证：切换 &lt;code&gt;n/m/y&lt;/code&gt; 后，Kbuild 会选择不编译、生成 &lt;code&gt;.ko&lt;/code&gt; 或进入 &lt;code&gt;vmlinux&lt;/code&gt;。目标内核配置或构建目录改变后，外部模块则应对匹配的新构建树做干净重建，不要复用旧 &lt;code&gt;.o&lt;/code&gt;。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Important&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;code&gt;.cmd&lt;/code&gt; 是派生状态，不是稳定接口。需要诊断时可以读它，不能把项目逻辑建立在它的具体文本格式上。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;modpost&lt;/code&gt; 不是又一个链接器&lt;a href=&quot;#modpost-不是又一个链接器&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;第一阶段结束时，我们已经有 &lt;code&gt;pocket_sensor.o&lt;/code&gt;、&lt;code&gt;pocket_sensor.mod&lt;/code&gt; 和 &lt;code&gt;modules.order&lt;/code&gt;。&lt;code&gt;modules.order&lt;/code&gt; 在这个实验里只有：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它记录 Kbuild 发现模块的顺序。随后 &lt;code&gt;scripts/Makefile.modpost&lt;/code&gt; 调用主机程序 &lt;code&gt;scripts/mod/modpost&lt;/code&gt;。&lt;code&gt;modpost&lt;/code&gt; 主要做静态审查和元数据生成：&lt;/p&gt;
































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;阶段&lt;/th&gt;&lt;th&gt;&lt;code&gt;modpost&lt;/code&gt; 的工作&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;符号扫描&lt;/td&gt;&lt;td&gt;扫描导入、导出、&lt;code&gt;EXPORT_SYMBOL()&lt;/code&gt; 和命名空间关系&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;符号检查&lt;/td&gt;&lt;td&gt;对照内核 &lt;code&gt;Module.symvers&lt;/code&gt; 检查未解析符号&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;版本处理&lt;/td&gt;&lt;td&gt;在启用 &lt;code&gt;CONFIG_MODVERSIONS&lt;/code&gt; 时处理符号 CRC&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;静态检查&lt;/td&gt;&lt;td&gt;执行 section mismatch 等检查&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;代码生成&lt;/td&gt;&lt;td&gt;为每个模块生成 &lt;code&gt;&amp;lt;module&amp;gt;.mod.c&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;符号输出&lt;/td&gt;&lt;td&gt;为当前构建写出 &lt;code&gt;Module.symvers&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这里有一个很好用的失败实验。我最初只构建了 &lt;code&gt;bzImage&lt;/code&gt;，没有构建内核的 &lt;code&gt;modules&lt;/code&gt; 目标，于是目标输出树中缺少 &lt;code&gt;Module.symvers&lt;/code&gt;。&lt;code&gt;modpost&lt;/code&gt; 没有含糊地吐出一个坏 &lt;code&gt;.ko&lt;/code&gt;，而是列出：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;WARNING: .../Module.symvers is missing.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR: modpost: &quot;param_ops_uint&quot; [pocket_sensor.ko] undefined!&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR: modpost: &quot;__kmalloc_noprof&quot; [pocket_sensor.ko] undefined!&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR: modpost: &quot;kfree&quot; [pocket_sensor.ko] undefined!&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR: modpost: &quot;_printk&quot; [pocket_sensor.ko] undefined!&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR: modpost: &quot;param_ops_charp&quot; [pocket_sensor.ko] undefined!&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;运行内核的 &lt;code&gt;make modules&lt;/code&gt; 生成 &lt;code&gt;Module.symvers&lt;/code&gt; 后，同一个模块通过。这正是 &lt;code&gt;modpost&lt;/code&gt; 的价值：在 QEMU 启动前就指出内核符号账本不完整。&lt;/p&gt;&lt;p&gt;Linux 6.18 为我的模块生成了很短的 &lt;code&gt;pocket_sensor.mod.c&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;linux/module.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;linux/export-internal.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;linux/compiler.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MODULE_INFO&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; KBUILD_MODNAME&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;__visible &lt;/span&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; module __this_module&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;__section&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.gnu.linkonce.this_module&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; KBUILD_MODNAME,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.init &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; init_module,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#ifdef&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CONFIG_MODULE_UNLOAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.exit &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cleanup_module,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.arch &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MODULE_ARCH_INIT,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MODULE_INFO&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;depends&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这段代码把模块名、入口、退出函数和依赖写成内核加载器认识的结构。它被编译为 &lt;code&gt;pocket_sensor.mod.o&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;旧文章常把 &lt;code&gt;vermagic&lt;/code&gt; 也指向 &lt;code&gt;&amp;lt;module&amp;gt;.mod.c&lt;/code&gt;。Linux 6.18 已经把公共部分放进 &lt;code&gt;scripts/module-common.c&lt;/code&gt;，编译为 &lt;code&gt;.module-common.o&lt;/code&gt;；其中包含：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MODULE_INFO&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;vermagic&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; VERMAGIC_STRING&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;所以最终输入比常见简图多一个对象：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ld -r&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor.o&lt;br /&gt;&lt;br /&gt;模块代码&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Makefile.modfinal&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor.mod.o&lt;br /&gt;&lt;br /&gt;入口、退出与依赖&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;.module-common.o&lt;br /&gt;&lt;br /&gt;vermagic 等公共元数据&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/module.lds&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor.ko&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ld -r&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor.o&lt;br /&gt;&lt;br /&gt;模块代码&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Makefile.modfinal&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor.mod.o&lt;br /&gt;&lt;br /&gt;入口、退出与依赖&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;.module-common.o&lt;br /&gt;&lt;br /&gt;vermagic 等公共元数据&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/module.lds&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor.ko&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;版本锁定在这里立刻有了价值。机制没变，但元数据从哪一个生成文件进入 &lt;code&gt;.ko&lt;/code&gt; 已经变了。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;.ko&lt;/code&gt; 仍然是一个可重定位 ELF&lt;a href=&quot;#ko-仍然是一个可重定位-elf&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;scripts/Makefile.modfinal&lt;/code&gt; 最终执行近似下面的链接：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ld&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-r&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-T&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scripts/module.lds&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pocket_sensor.ko&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;pocket_sensor.o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pocket_sensor.mod.o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.module-common.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;readelf -h pocket_sensor.ko&lt;/code&gt; 给出：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Class:   ELF64&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Data:    little endian&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Type:    REL (Relocatable file)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Machine: Advanced Micro Devices X86-64&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Entry:   0x0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它没有用户程序那种固定入口地址，也还没有落到最终运行地址。&lt;code&gt;readelf -SW&lt;/code&gt; 在 6944 字节中找到了 31 个 section，其中最关键的是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.init.text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.exit.text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.modinfo&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;__param&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.gnu.linkonce.this_module&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.rela.text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.rela.init.text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.rela.exit.text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;__init&lt;/code&gt; 把初始化函数放进 &lt;code&gt;.init.text&lt;/code&gt;，&lt;code&gt;__exit&lt;/code&gt; 把退出函数放进 &lt;code&gt;.exit.text&lt;/code&gt;。&lt;code&gt;module_param()&lt;/code&gt; 产生 &lt;code&gt;__param&lt;/code&gt; 数据，&lt;code&gt;MODULE_*&lt;/code&gt; 宏向 &lt;code&gt;.modinfo&lt;/code&gt; 填字符串，链接器保留 relocation section 等待内核处理。&lt;/p&gt;&lt;p&gt;&lt;code&gt;readelf -Ws&lt;/code&gt; 还能看到尚未解析的内核符号：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;UND param_ops_uint&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;UND __kmalloc_noprof&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;UND kfree&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;UND _printk&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;UND param_ops_charp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这不是链接失败留下的垃圾。模块必须调用当前内核提供的分配器、日志和参数实现，所以 &lt;code&gt;.ko&lt;/code&gt; 有意保留这些外部引用。&lt;code&gt;modpost&lt;/code&gt; 已用 &lt;code&gt;Module.symvers&lt;/code&gt; 检查它们是否合法，内核加载器稍后给它们填入运行地址。&lt;/p&gt;&lt;p&gt;&lt;code&gt;modinfo&lt;/code&gt; 则把 &lt;code&gt;.modinfo&lt;/code&gt; 翻译成人容易读的结果：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;filename:    pocket_sensor.ko&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;license:     GPL&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;description: Small Kbuild experiment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;author:      bfmhno3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;depends:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;name:        pocket_sensor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vermagic:    6.18.0 mod_unload&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;parm:        label:Sensor label (charp)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;parm:        samples:Number of synthetic samples (uint)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;到这里，&lt;code&gt;.ko&lt;/code&gt; 已经不神秘了。它是一个附带模块元数据、参数描述、未解析内核符号和重定位记录的 ELF relocatable object。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我把它真的装进了 Linux 6.18&lt;a href=&quot;#我把它真的装进了-linux-618&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我没有向宿主机加载实验模块。宿主机是工作环境，不是祭坛。我构建了一个精简的 x86_64 Linux 6.18、一个 &lt;code&gt;1,422,848&lt;/code&gt; 字节的 initramfs，然后用 QEMU 启动：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;qemu-system-x86_64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-machine&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;accel=tcg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-cpu&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;256M&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-smp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-nographic&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-no-reboot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-kernel&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arch/x86/boot/bzImage&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-initrd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;initramfs.cpio&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-append&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;console=ttyS0 rdinit=/init panic=-1&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;/init&lt;/code&gt; 挂载 &lt;code&gt;/proc&lt;/code&gt; 和 &lt;code&gt;/sys&lt;/code&gt;，然后执行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;insmod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/pocket_sensor.ko&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;label=qemu&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;samples=&lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/sys/module/pocket_sensor/parameters/label&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/sys/module/pocket_sensor/parameters/samples&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rmmod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pocket_sensor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;串口日志是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;=== pocket_sensor experiment ===&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor: loading out-of-tree module taints kernel.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor: loaded label=qemu samples=7&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;label=qemu&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;samples=7&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor: unloaded samples=7&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;=== experiment complete ===&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;两个参数真的进入 &lt;code&gt;/sys/module/pocket_sensor/parameters/&lt;/code&gt;，&lt;code&gt;stats.c&lt;/code&gt; 分配的 7 个槽位在卸载时释放，初始化和退出日志各出现一次。外部模块 taint 也符合预期；它表示内核装入了树外代码，不表示实验失败。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;从 &lt;code&gt;insmod&lt;/code&gt; 到 &lt;code&gt;module_init()&lt;/code&gt;&lt;a href=&quot;#从-insmod-到-module_init&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;insmod&lt;/code&gt; 很薄。现代 kmod 通常打开模块文件并调用 &lt;code&gt;finit_module()&lt;/code&gt;；内核也保留接收用户缓冲区的 &lt;code&gt;init_module()&lt;/code&gt; syscall。Linux 6.18 的核心路径在 &lt;code&gt;kernel/module/main.c&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;insmod pocket_sensor.ko&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;finit_module()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;load_module()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;module_sig_check()&lt;br /&gt;&lt;br /&gt;签名与策略&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ELF、modinfo 与 vermagic 检查&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;layout_and_allocate()&lt;br /&gt;&lt;br /&gt;布局并分配内存&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;simplify_symbols()&lt;br /&gt;&lt;br /&gt;解析内核符号&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;apply_relocations()&lt;br /&gt;&lt;br /&gt;应用重定位&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;解析模块参数并建立 sysfs&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;do_init_module()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor_init()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;insmod pocket_sensor.ko&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;finit_module()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;load_module()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;module_sig_check()&lt;br /&gt;&lt;br /&gt;签名与策略&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;ELF、modinfo 与 vermagic 检查&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;layout_and_allocate()&lt;br /&gt;&lt;br /&gt;布局并分配内存&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;simplify_symbols()&lt;br /&gt;&lt;br /&gt;解析内核符号&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;apply_relocations()&lt;br /&gt;&lt;br /&gt;应用重定位&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;解析模块参数并建立 sysfs&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;do_init_module()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor_init()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;load_module()&lt;/code&gt; 先检查签名，再验证 ELF 和 &lt;code&gt;vermagic&lt;/code&gt;，计算各 section 布局并分配模块内存。&lt;code&gt;simplify_symbols()&lt;/code&gt; 遍历 &lt;code&gt;SHN_UNDEF&lt;/code&gt; 符号，把 &lt;code&gt;_printk&lt;/code&gt;、&lt;code&gt;kfree&lt;/code&gt; 等名字解析成内核或其他已加载模块中的地址。&lt;code&gt;apply_relocations()&lt;/code&gt; 再按架构处理 &lt;code&gt;.rela.*&lt;/code&gt;，把地址写进刚分配的代码和数据。&lt;/p&gt;&lt;p&gt;参数解析发生在入口函数之前。因此 &lt;code&gt;label=qemu samples=7&lt;/code&gt; 已经写入静态变量，&lt;code&gt;pocket_sensor_init()&lt;/code&gt; 一开始就能读。内核还根据 &lt;code&gt;__param&lt;/code&gt; 建立 sysfs 属性。最后 &lt;code&gt;do_init_module()&lt;/code&gt; 调用 &lt;code&gt;.mod.c&lt;/code&gt; 中记录的 &lt;code&gt;init_module&lt;/code&gt;，也就是宏展开后的 &lt;code&gt;pocket_sensor_init()&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;code&gt;insmod&lt;/code&gt; 只加载指定文件，不主动解决依赖。真实驱动有依赖时通常用 &lt;code&gt;modprobe&lt;/code&gt;，让它根据模块索引先加载依赖；但无论用户空间怎么安排文件，最终都进入上面的内核加载路径。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;4 类错误分别在说什么？&lt;a href=&quot;#4-类错误分别在说什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;模块错误经常只在终端留下一句短话，真正上下文在内核日志。我的排查顺序是先看 &lt;code&gt;dmesg&lt;/code&gt;，再把错误放回加载链。&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;用户空间错误&lt;/th&gt;&lt;th&gt;加载阶段&lt;/th&gt;&lt;th&gt;首要检查&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Invalid module format&lt;/code&gt;&lt;/td&gt;&lt;td&gt;ELF、&lt;code&gt;vermagic&lt;/code&gt; 或符号版本检查&lt;/td&gt;&lt;td&gt;架构、目标构建目录、内核配置和 &lt;code&gt;dmesg&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Unknown symbol&lt;/code&gt;&lt;/td&gt;&lt;td&gt;符号解析&lt;/td&gt;&lt;td&gt;依赖模块、导出符号、命名空间和 &lt;code&gt;Module.symvers&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Operation not permitted&lt;/code&gt;&lt;/td&gt;&lt;td&gt;权限或安全策略&lt;/td&gt;&lt;td&gt;&lt;code&gt;CAP_SYS_MODULE&lt;/code&gt;、&lt;code&gt;modules_disabled&lt;/code&gt;、LSM 和 lockdown&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Required key not available&lt;/code&gt;&lt;/td&gt;&lt;td&gt;模块签名验证&lt;/td&gt;&lt;td&gt;签名、可信 keyring 和强制签名策略&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;Invalid module format&lt;/code&gt;&lt;a href=&quot;#invalid-module-format&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;它通常对应 &lt;code&gt;ENOEXEC&lt;/code&gt;，但原因不只一个。常见情况包括：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.ko&lt;/code&gt; 的架构或 ELF 结构不符合当前内核。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vermagic&lt;/code&gt; 不匹配。&lt;/li&gt;
&lt;li&gt;启用 &lt;code&gt;CONFIG_MODVERSIONS&lt;/code&gt; 后，关键符号 CRC 不匹配。&lt;/li&gt;
&lt;li&gt;生成 &lt;code&gt;struct module&lt;/code&gt; 的构建配置与运行内核不一致。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Linux 6.18 的 &lt;code&gt;check_modinfo()&lt;/code&gt; 会比较模块和内核的 magic，并在日志中打印两边字符串。正确修复是用运行内核对应的构建目录和配置重新编译，不是删掉 &lt;code&gt;vermagic&lt;/code&gt; 或使用强制加载把检查蒙住。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;Unknown symbol&lt;/code&gt;&lt;a href=&quot;#unknown-symbol&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;simplify_symbols()&lt;/code&gt; 无法为某个 &lt;code&gt;SHN_UNDEF&lt;/code&gt; 找到可用的导出符号。可能是依赖模块没有加载、目标内核没导出该符号、命名空间导入缺失，或外部模块调用了不该调用的内部接口。&lt;/p&gt;&lt;p&gt;先用 &lt;code&gt;modinfo&lt;/code&gt; 查看依赖，再检查 &lt;code&gt;Module.symvers&lt;/code&gt; 和目标内核配置。若 &lt;code&gt;modpost&lt;/code&gt; 已经警告 unresolved symbol，不要期待运行时突然心软。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;Operation not permitted&lt;/code&gt;&lt;a href=&quot;#operation-not-permitted&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;加载入口先执行 &lt;code&gt;may_init_module()&lt;/code&gt;。Linux 6.18 在调用者缺少 &lt;code&gt;CAP_SYS_MODULE&lt;/code&gt; 或 &lt;code&gt;modules_disabled=1&lt;/code&gt; 时返回 &lt;code&gt;EPERM&lt;/code&gt;；LSM、lockdown 和容器策略也可能拒绝加载。&lt;code&gt;sudo&lt;/code&gt; 只能解决普通权限，不会自动推翻 Secure Boot lockdown 或宿主机给容器划下的边界。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;Required key not available&lt;/code&gt; 或 &lt;code&gt;Key was rejected by service&lt;/code&gt;&lt;a href=&quot;#required-key-not-available-或-key-was-rejected-by-service&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;启用强制模块签名后，&lt;code&gt;module_sig_check()&lt;/code&gt; 会验证附在模块末尾的 PKCS#7 签名。模块未签名、算法不支持或密钥不在可信 keyring 中时，Linux 6.18 的强制路径返回 &lt;code&gt;EKEYREJECTED&lt;/code&gt;，用户空间显示的短句随 errno 翻译和加载路径而异。正确路径是用目标系统信任的密钥签名并部署证书，不是关闭检查。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;强制加载、伪造版本字符串和关闭签名策略会把”构建产物不匹配”变成”内核正在执行未经验证的代码”。它们不是调试捷径，而是在移除护栏。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;把 &lt;code&gt;obj-m&lt;/code&gt; 换成 &lt;code&gt;obj-y&lt;/code&gt; 会发生什么？&lt;a href=&quot;#把-obj-m-换成-obj-y-会发生什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;模块路径现在完整了，但上一篇真正留下的问题还有一半：&lt;code&gt;CONFIG_FOO=y&lt;/code&gt; 去了哪里？&lt;/p&gt;&lt;p&gt;对树内目录，父级 Makefile 写：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;CONFIG_POCKET_SENSOR&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; pocket_sensor/&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;当值为 &lt;code&gt;y&lt;/code&gt;，&lt;code&gt;scripts/Makefile.build&lt;/code&gt; 递归进入目录，并把子目录的内建对象装进：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;drivers/misc/pocket_sensor/built-in.a&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Linux 6.18 使用：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ar cDPrST built-in.a ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;其中 &lt;code&gt;T&lt;/code&gt; 创建 thin archive。它主要保存成员路径，不复制每个 &lt;code&gt;.o&lt;/code&gt; 的完整内容。我的归档由两个成员组成：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;drivers/misc/pocket_sensor/core.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;drivers/misc/pocket_sensor/stats.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;目录层级继续向上汇聚：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;core.o + stats.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor/built-in.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;drivers/misc/built-in.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;drivers/built-in.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;根 built-in.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;vmlinux.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;vmlinux.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/link-vmlinux.sh&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;vmlinux&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;core.o + stats.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor/built-in.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;drivers/misc/built-in.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;drivers/built-in.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;根 built-in.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;vmlinux.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;vmlinux.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/link-vmlinux.sh&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;vmlinux&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;顶层 Makefile 用 &lt;code&gt;KBUILD_VMLINUX_OBJS&lt;/code&gt; 生成 &lt;code&gt;vmlinux.a&lt;/code&gt;，&lt;code&gt;scripts/Makefile.vmlinux_o&lt;/code&gt; 通过 &lt;code&gt;--whole-archive vmlinux.a&lt;/code&gt; 生成 &lt;code&gt;vmlinux.o&lt;/code&gt;，&lt;code&gt;scripts/link-vmlinux.sh&lt;/code&gt; 再完成架构相关的最终链接。&lt;code&gt;y&lt;/code&gt; 状态不会生成或更新 &lt;code&gt;pocket_sensor.ko&lt;/code&gt;；若刚从 &lt;code&gt;m&lt;/code&gt; 切到 &lt;code&gt;y&lt;/code&gt;，输出树里可能仍留着旧 &lt;code&gt;.ko&lt;/code&gt;，它只是陈旧产物，&lt;code&gt;make clean&lt;/code&gt; 才会移除。&lt;/p&gt;&lt;p&gt;&lt;code&gt;modules.builtin&lt;/code&gt; 中仍会记录一个逻辑路径：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;kernel/drivers/misc/pocket_sensor/pocket_sensor.ko&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它告诉 &lt;code&gt;modprobe&lt;/code&gt; 这个模块名已经内建，不表示磁盘上真的生成了那份 &lt;code&gt;.ko&lt;/code&gt;。我的 &lt;code&gt;System.map&lt;/code&gt; 则能找到 &lt;code&gt;pocket_sensor_init&lt;/code&gt;、&lt;code&gt;pocket_stats_start&lt;/code&gt; 和对应 initcall，证明代码已经进入 &lt;code&gt;vmlinux&lt;/code&gt;。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;内建时 &lt;code&gt;module_init()&lt;/code&gt; 变成 initcall&lt;a href=&quot;#内建时-module_init-变成-initcall&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;构建形态&lt;/th&gt;&lt;th&gt;&lt;code&gt;module_init()&lt;/code&gt; 的结果&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;可加载模块&lt;/td&gt;&lt;td&gt;提供 &lt;code&gt;.mod.c&lt;/code&gt; 所引用的 &lt;code&gt;init_module&lt;/code&gt; 入口&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;内建代码&lt;/td&gt;&lt;td&gt;映射到 &lt;code&gt;device_initcall()&lt;/code&gt;，把函数指针放进 initcall section&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Linux 按 initcall level 执行；同一 level 内的次序受链接顺序影响。&lt;code&gt;obj-y&lt;/code&gt; 的排列因此不只是审美问题。两个驱动若偷偷依赖初始化先后，调换 Makefile 中对象顺序就可能改变启动行为。更好的设计仍是显式表达依赖，而不是拿链接顺序当隐藏 API。&lt;/p&gt;&lt;p&gt;&lt;code&gt;module_exit()&lt;/code&gt; 对可卸载模块是 &lt;code&gt;rmmod&lt;/code&gt; 路径。内建代码正常运行期间不会被卸载，所以退出函数没有对应的日常生命周期意义；我的 &lt;code&gt;System.map&lt;/code&gt; 仍保留了 &lt;code&gt;pocket_sensor_exit&lt;/code&gt;，但内核不会把它当作可卸载模块的退出回调来调用。不要因为同一份源码能用 &lt;code&gt;y&lt;/code&gt; 和 &lt;code&gt;m&lt;/code&gt; 编译，就假设两种生命周期完全对称。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我把外部模块迁回了内核树&lt;a href=&quot;#我把外部模块迁回了内核树&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;实验目录改为：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;drivers/misc/pocket_sensor/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── Kconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── Makefile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── core.c&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── stats.c&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└── pocket_sensor.h&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;父级 &lt;code&gt;drivers/misc/Kconfig&lt;/code&gt; 加一行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;source &quot;drivers/misc/pocket_sensor/Kconfig&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;配置文件为：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;GPL-2.0-only&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config POCKET_SENSOR&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tristate &quot;Pocket sensor experiment&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Build the pocket sensor used to trace the Kbuild pipeline.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config POCKET_SENSOR_STATS&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool &quot;Pocket sensor statistics&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;depends on POCKET_SENSOR&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;default y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Add the synthetic sample storage used by the experiment.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;父级 &lt;code&gt;drivers/misc/Makefile&lt;/code&gt; 决定是否进入目录：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;CONFIG_POCKET_SENSOR&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; pocket_sensor/&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;子目录 Makefile 决定总成和可选零件：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;GPL-2.0-only&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;CONFIG_POCKET_SENSOR&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; pocket_sensor.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor-y&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; core.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pocket_sensor-&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;CONFIG_POCKET_SENSOR_STATS&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; stats.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;当 statistics 关闭时，头文件提供不分配内存的 inline 实现；当它打开时，&lt;code&gt;stats.o&lt;/code&gt; 进入复合对象。这样配置和链接图一致，不会让 &lt;code&gt;core.o&lt;/code&gt; 留下无人提供的函数。&lt;/p&gt;&lt;p&gt;我在同一个 Linux 6.18 输出树里验证了 3 种状态：&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;配置&lt;/th&gt;&lt;th&gt;实际构建结果&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;CONFIG_POCKET_SENSOR=n&lt;/code&gt;&lt;/td&gt;&lt;td&gt;没有创建 &lt;code&gt;drivers/misc/pocket_sensor/&lt;/code&gt; 输出目录&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;CONFIG_POCKET_SENSOR=m&lt;/code&gt;&lt;/td&gt;&lt;td&gt;编译 &lt;code&gt;core.o&lt;/code&gt;、&lt;code&gt;stats.o&lt;/code&gt;，部分链接并生成 &lt;code&gt;pocket_sensor.ko&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;CONFIG_POCKET_SENSOR=y&lt;/code&gt;&lt;/td&gt;&lt;td&gt;两个对象进入 thin &lt;code&gt;built-in.a&lt;/code&gt;，随后进入 &lt;code&gt;vmlinux&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;code&gt;y&lt;/code&gt; 状态的增量构建日志把路径完整打印出来：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CC      drivers/misc/pocket_sensor/core.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CC      drivers/misc/pocket_sensor/stats.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;AR      drivers/misc/pocket_sensor/built-in.a&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;AR      drivers/misc/built-in.a&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;AR      drivers/built-in.a&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;AR      built-in.a&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;AR      vmlinux.a&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;LD      vmlinux.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MODPOST vmlinux.symvers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;LD      vmlinux.unstripped&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;OBJCOPY vmlinux&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这也把两篇文章真正接上了：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;n&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;m&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;y&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Kconfig&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;CONFIG_POCKET_SENSOR&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;不进入构建&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;obj-m&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;modpost&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor.ko&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;obj-y&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;built-in.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;vmlinux.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;vmlinux&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;n&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;m&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;y&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Kconfig&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;CONFIG_POCKET_SENSOR&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;不进入构建&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;obj-m&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor.o&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;modpost&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pocket_sensor.ko&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;obj-y&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;built-in.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;vmlinux.a&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;vmlinux&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;外部模块和树内模块的维护边界&lt;a href=&quot;#外部模块和树内模块的维护边界&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;两条路径共享 Kbuild 语法和大量编译规则，但维护契约不同。&lt;/p&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;外部模块&lt;/th&gt;&lt;th&gt;树内模块&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;适合场景&lt;/td&gt;&lt;td&gt;快速验证、尚未上游的厂商驱动、独立发布或 DKMS&lt;/td&gt;&lt;td&gt;与内核同步演进、接入子系统测试、准备上游或长期维护产品内核&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;版本关系&lt;/td&gt;&lt;td&gt;内核内部 API 和 ABI 变化后，可能需要重编或修改源码&lt;/td&gt;&lt;td&gt;接口与调用方可以在同一次内核提交中原子修改&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;构建要求&lt;/td&gt;&lt;td&gt;构建目录、&lt;code&gt;.config&lt;/code&gt;、&lt;code&gt;Module.symvers&lt;/code&gt; 和工具链必须匹配目标内核&lt;/td&gt;&lt;td&gt;由同一内核源码树、配置和 Kbuild 图统一构建&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;安全与交付&lt;/td&gt;&lt;td&gt;需要单独处理安装、依赖索引、Secure Boot 签名和密钥部署&lt;/td&gt;&lt;td&gt;跟随内核镜像或模块安装流程交付&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;集成成本&lt;/td&gt;&lt;td&gt;初期低，但升级兼容工作由模块维护者承担&lt;/td&gt;&lt;td&gt;初期要补齐 Kconfig、测试、文档和子系统审查&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;DKMS 解决的是自动重编与安装流程，不会把不稳定的内核 ABI 变稳定。&lt;/p&gt;&lt;p&gt;树内也不是把目录复制到 &lt;code&gt;drivers/&lt;/code&gt; 就结束。正式驱动还要遵守子系统接口、代码风格、&lt;code&gt;MAINTAINERS&lt;/code&gt;、文档与设备绑定规范，并写出准确的 Kconfig 依赖。我的 &lt;code&gt;pocket_sensor&lt;/code&gt; 故意没有 platform bus、Device Tree、I2C 或真实寄存器访问；伪造硬件只会让 Kbuild 主线被一堆无意义的驱动样板淹没。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;ARM64 工程只多哪一层？&lt;a href=&quot;#arm64-工程只多哪一层&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Kbuild 数据流不因架构改变。交叉构建 ARM64 外部模块时，通常在内核配置、内核构建和模块构建中保持同一组变量：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O=&quot;&lt;/span&gt;&lt;span&gt;$KOUT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;ARCH=arm64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;CROSS_COMPILE=aarch64-linux-gnu-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;my_board_defconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O=&quot;&lt;/span&gt;&lt;span&gt;$KOUT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;ARCH=arm64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;CROSS_COMPILE=aarch64-linux-gnu-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-j&lt;/span&gt;&lt;span&gt;&quot;$(&lt;/span&gt;&lt;span&gt;nproc&lt;/span&gt;&lt;span&gt;)&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-C&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$KOUT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;M=&quot;&lt;/span&gt;&lt;span&gt;$PWD&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;ARCH=arm64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;CROSS_COMPILE=aarch64-linux-gnu-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;modules&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;新增的是目标架构、交叉工具链和真实板卡的启动链，不是第二套 &lt;code&gt;obj-m -&amp;gt; modpost -&amp;gt; .ko&lt;/code&gt; 机制。加载时仍要保证运行内核就是这份配置和符号账本对应的内核。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;继续往下追&lt;a href=&quot;#继续往下追&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这次我从 2 行 Kbuild 得到了一个 &lt;code&gt;6944&lt;/code&gt; 字节的 ELF，在 QEMU 中给它传入 2 个参数，观察它分配 7 个样本槽位，再完整卸载。沿途最让我意外的不是链接器，而是那些小账本：&lt;code&gt;.cmd&lt;/code&gt; 记住命令和依赖，&lt;code&gt;pocket_sensor.mod&lt;/code&gt; 记住复合对象，&lt;code&gt;modules.order&lt;/code&gt; 记住模块顺序，&lt;code&gt;Module.symvers&lt;/code&gt; 记住内核可见符号，&lt;code&gt;.mod.c&lt;/code&gt; 把入口写回 C。&lt;/p&gt;&lt;p&gt;如果继续向下走，下一站应该是真实总线和设备模型：让同一个模块不再凭 &lt;code&gt;insmod&lt;/code&gt; 自说自话，而是由 Device Tree 描述硬件，由 platform 或 I2C core 完成匹配，再看 probe、defer 和 remove 如何与 Kbuild 产物相遇。到那时 &lt;code&gt;.ko&lt;/code&gt; 已经不是神秘黑盒，只是驱动生命周期被装进 ELF 后的运输形态。&lt;/p&gt;&lt;p&gt;很好。我现在可以停止盯着 &lt;code&gt;obj-m&lt;/code&gt; 猜 Make 的心情，开始给那块并不存在的传感器寻找一块真正的板子了。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/Documentation/kbuild/modules.rst&quot; target=&quot;_blank&quot;&gt;Linux 6.18: Building External Modules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/Documentation/kbuild/makefiles.rst&quot; target=&quot;_blank&quot;&gt;Linux 6.18: Linux Kernel Makefiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/scripts/Kbuild.include&quot; target=&quot;_blank&quot;&gt;Linux 6.18: scripts/Kbuild.include&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/scripts/Makefile.build&quot; target=&quot;_blank&quot;&gt;Linux 6.18: scripts/Makefile.build&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/scripts/Makefile.lib&quot; target=&quot;_blank&quot;&gt;Linux 6.18: scripts/Makefile.lib&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/scripts/Makefile.modpost&quot; target=&quot;_blank&quot;&gt;Linux 6.18: scripts/Makefile.modpost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/scripts/Makefile.modfinal&quot; target=&quot;_blank&quot;&gt;Linux 6.18: scripts/Makefile.modfinal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/scripts/module-common.c&quot; target=&quot;_blank&quot;&gt;Linux 6.18: scripts/module-common.c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/scripts/link-vmlinux.sh&quot; target=&quot;_blank&quot;&gt;Linux 6.18: scripts/link-vmlinux.sh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/kernel/module/main.c&quot; target=&quot;_blank&quot;&gt;Linux 6.18: kernel/module/main.c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/Documentation/admin-guide/module-signing.rst&quot; target=&quot;_blank&quot;&gt;Linux 6.18: Module signing facility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded></item><item><title>我拆开了 Kconfig: 从一个选项到 U-Boot 和 Linux 的条件编译</title><link>https://bfmhno3.github.io/posts/understanding-kconfig/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/understanding-kconfig/</guid><description>我用 Linux 6.18 的 Kconfig 前端搭了一个 30 行的实验板级配置，再沿着 Kconfig、defconfig、.config、auto.conf、autoconf.h 和 Kbuild 追踪 U-Boot 与 Linux 到底如何裁剪代码。</description><pubDate>Wed, 02 Sep 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我最近需要自己裁剪并构建 U-Boot 和 Linux Kernel。两个项目都让我先选一个 &lt;code&gt;defconfig&lt;/code&gt;，再面对 &lt;code&gt;menuconfig&lt;/code&gt; 里几千个选项，最后得到一片 &lt;code&gt;CONFIG_FOO=y&lt;/code&gt;。我会用这些命令，却不知道中间发生了什么。于是我取出 Linux 6.18 的 Kconfig 实现，写了一棵只有 7 个符号的配置树，并一路追到 Makefile 和 C 预处理器。30 行 Kconfig 之后，这套系统终于不再像一个蓝色背景的许愿池。&lt;/p&gt;
&lt;p&gt;本文以 &lt;strong&gt;Linux 6.18&lt;/strong&gt; 和 &lt;strong&gt;U-Boot v2026.07&lt;/strong&gt; 为准。Linux 6.18 是 2025 年 11 月 30 日发布的 longterm 内核，预计维护到 2028 年 12 月；U-Boot v2026.07 则是写作时适合新项目参考的稳定版本。Kconfig 的核心模型很稳定，但具体符号、默认值和 U-Boot 的兼容生成步骤会随版本变化，所以我会把机制和某个板子的配置内容分开。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先纠正一个名字&lt;a href=&quot;#先纠正一个名字&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Kconfig 不是完整的构建系统。&lt;/p&gt;&lt;p&gt;我更愿意把整个过程分成 3 层：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Kconfig&lt;/strong&gt; 描述有哪些配置符号、它们的类型、默认值和约束，并求出一份合法配置。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kbuild/Makefile&lt;/strong&gt; 读取配置，决定哪些目录和 &lt;code&gt;.o&lt;/code&gt; 文件参加编译，以及传递哪些编译选项。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编译器和链接器&lt;/strong&gt; 根据宏继续裁剪 C 代码，最后生成内核或 U-Boot 镜像。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Kconfig 像配电柜：它决定哪些回路通电，但并不亲自制造电器。&lt;code&gt;make menuconfig&lt;/code&gt; 只是配电柜的一块操作面板，不是配电柜本身，更不是发电厂。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Kconfig 文件&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;conf/mconf 等前端&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;defconfig 或旧 .config&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;完整 .config&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;include/config/auto.conf&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;include/generated/autoconf.h&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Kbuild/Makefile 选择目标文件&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;C 预处理器选择代码&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;编译与链接&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Kconfig 文件&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;conf/mconf 等前端&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;defconfig 或旧 .config&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;完整 .config&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;include/config/auto.conf&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;include/generated/autoconf.h&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Kbuild/Makefile 选择目标文件&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;C 预处理器选择代码&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;编译与链接&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这张图是本文最重要的地图。后面的每个文件都只是在这条数据流上承担一个角色。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我先造一个口袋大小的配置系统&lt;a href=&quot;#我先造一个口袋大小的配置系统&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我使用 Linux 6.18 自带的 &lt;code&gt;scripts/kconfig/&lt;/code&gt;，因为 U-Boot 的 Kconfig 语言源自 Linux，而 Linux 版本还保留了 &lt;code&gt;m&lt;/code&gt; 这个最容易暴露三值逻辑的状态。先取得源码：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--depth&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--branch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v6.18&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;https://github.com/torvalds/linux.git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;linux-6.18&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;linux-6.18&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lab/drivers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Kconfig 前端需要主机上的 C 编译器、GNU Make、Flex 和 Bison。&lt;code&gt;menuconfig&lt;/code&gt; 还需要 ncurses 开发文件。这里只运行非交互的 &lt;code&gt;alldefconfig&lt;/code&gt;，所以先不碰 ncurses。&lt;/p&gt;&lt;p&gt;根配置 &lt;code&gt;lab/Kconfig&lt;/code&gt; 如下：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;mainmenu &quot;Pocket Board Configuration&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config MODULES&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool &quot;Enable loadable modules&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;modules&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;default y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;menu &quot;Board&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config SOC_ALPHA&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool &quot;Alpha SoC&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;default y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config HAS_I2C&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;default y if SOC_ALPHA&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config LOG_LEVEL&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;int &quot;Log level&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;range 0 4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;default 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;endmenu&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;source &quot;lab/drivers/Kconfig&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;驱动配置放在 &lt;code&gt;lab/drivers/Kconfig&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;menu &quot;Drivers&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config SENSOR&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tristate &quot;Temperature sensor&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;depends on HAS_I2C&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;default m&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config SENSOR_DEBUG&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool &quot;Verbose sensor debugging&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;depends on SENSOR&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config APP&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool &quot;Sensor application&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;select SENSOR_CORE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config SENSOR_CORE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;depends on HAS_I2C&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;endmenu&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;现在让 Linux 的构建入口编译 Kconfig 前端，并用所有符号的默认值求解一次：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O=&quot;&lt;/span&gt;&lt;span&gt;$PWD&lt;/span&gt;&lt;span&gt;/out&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;KBUILD_KCONFIG=lab/Kconfig&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;alldefconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;O=&lt;/code&gt; 把生成物放进 &lt;code&gt;out/&lt;/code&gt;，&lt;code&gt;KBUILD_KCONFIG&lt;/code&gt; 把入口从内核根目录的 &lt;code&gt;Kconfig&lt;/code&gt; 换成我的实验文件。第一次运行还会把 &lt;code&gt;conf.c&lt;/code&gt;、解析器和词法分析器编译成主机程序 &lt;code&gt;out/scripts/kconfig/conf&lt;/code&gt;。换句话说，配置 Linux 之前，Linux 先在我的电脑上编译了一个小型配置求解器（构建系统也要先构建自己，乌龟下面还是乌龟）。&lt;/p&gt;&lt;p&gt;我得到的 &lt;code&gt;out/.config&lt;/code&gt; 核心部分是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_MODULES=y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_SOC_ALPHA=y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_HAS_I2C=y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_LOG_LEVEL=2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_SENSOR=m&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# CONFIG_SENSOR_DEBUG is not set&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# CONFIG_APP is not set&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里已经发生了几件值得追踪的事：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;我没有直接设置 &lt;code&gt;HAS_I2C&lt;/code&gt;，它从 &lt;code&gt;SOC_ALPHA=y&lt;/code&gt; 的条件默认值得到 &lt;code&gt;y&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SENSOR&lt;/code&gt; 的上限允许到 &lt;code&gt;y&lt;/code&gt;，默认值把它设成 &lt;code&gt;m&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SENSOR_DEBUG&lt;/code&gt; 没有显式 &lt;code&gt;default&lt;/code&gt;，所以默认为 &lt;code&gt;n&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;APP&lt;/code&gt; 同样默认为 &lt;code&gt;n&lt;/code&gt;，因此它还没有触发 &lt;code&gt;select&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Kconfig 文件不是按顺序执行的 Shell 脚本。解析器先收集符号与关系，再计算值、可见性和取值上下限。把它当成一个受约束的配置数据库，比把它当成一串赋值语句准确得多。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一个 &lt;code&gt;config&lt;/code&gt; 到底定义了什么？&lt;a href=&quot;#一个-config-到底定义了什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;最常见的条目是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config SENSOR&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tristate &quot;Temperature sensor&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;depends on HAS_I2C&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;default m&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Build support for the temperature sensor.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;SENSOR&lt;/code&gt; 是符号名。写入配置文件时，前端通常给它加上 &lt;code&gt;CONFIG_&lt;/code&gt; 前缀，于是得到 &lt;code&gt;CONFIG_SENSOR&lt;/code&gt;。Kconfig 源码中的依赖表达式仍然写 &lt;code&gt;SENSOR&lt;/code&gt;，不要写 &lt;code&gt;CONFIG_SENSOR&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;一个符号常见的属性包括：&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;属性&lt;/th&gt;&lt;th&gt;回答的问题&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;bool&lt;/code&gt; / &lt;code&gt;tristate&lt;/code&gt; / &lt;code&gt;string&lt;/code&gt; / &lt;code&gt;int&lt;/code&gt; / &lt;code&gt;hex&lt;/code&gt;&lt;/td&gt;&lt;td&gt;值属于什么类型？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;prompt&lt;/code&gt; 或类型后的字符串&lt;/td&gt;&lt;td&gt;用户能否在界面中看到并修改它？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;default&lt;/code&gt;&lt;/td&gt;&lt;td&gt;用户没有给值时，从哪里开始？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;depends on&lt;/code&gt;&lt;/td&gt;&lt;td&gt;这个符号允许取到多大的值？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;select&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当前符号启用时，强制抬高哪个符号的下限？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;imply&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当前符号启用时，弱建议哪个符号？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;range&lt;/code&gt;&lt;/td&gt;&lt;td&gt;整数或十六进制值允许在哪个区间？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;help&lt;/code&gt;&lt;/td&gt;&lt;td&gt;用户在配置界面请求帮助时看到什么？&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;类型后的提示文字其实是 &lt;code&gt;prompt&lt;/code&gt; 的简写：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;bool &quot;Alpha SoC&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;等价于：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;bool&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;prompt &quot;Alpha SoC&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这解释了一个常见现象：&lt;strong&gt;符号存在，不代表它会出现在 &lt;code&gt;menuconfig&lt;/code&gt;。&lt;/strong&gt; &lt;code&gt;HAS_I2C&lt;/code&gt; 和 &lt;code&gt;SENSOR_CORE&lt;/code&gt; 没有 prompt，它们是内部符号。用户不能直接操作它们，只能让 &lt;code&gt;default&lt;/code&gt;、&lt;code&gt;select&lt;/code&gt; 等关系计算出值。编辑 &lt;code&gt;.config&lt;/code&gt; 强塞一个值也不会把内部符号变成公开开关；下一次解析时，Kconfig 仍按它的定义处理。&lt;/p&gt;&lt;p&gt;&lt;code&gt;menu&lt;/code&gt;、&lt;code&gt;comment&lt;/code&gt; 和 &lt;code&gt;mainmenu&lt;/code&gt; 主要组织界面。&lt;code&gt;source&lt;/code&gt; 把另一份 Kconfig 文件读入同一棵配置树。它们不会因为排版漂亮就自动让任何 &lt;code&gt;.c&lt;/code&gt; 文件参加编译。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;n&lt;/code&gt;、&lt;code&gt;m&lt;/code&gt;、&lt;code&gt;y&lt;/code&gt; 不是 3 个字符串&lt;a href=&quot;#nmy-不是-3-个字符串&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Linux 的 &lt;code&gt;tristate&lt;/code&gt; 使用三值逻辑：&lt;/p&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;文本&lt;/th&gt;&lt;th&gt;数值&lt;/th&gt;&lt;th&gt;含义&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;n&lt;/code&gt;&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;不构建&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;m&lt;/code&gt;&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;构建为可加载模块&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;y&lt;/code&gt;&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;内建&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;表达式中的 &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; 取较小值，&lt;code&gt;||&lt;/code&gt; 取较大值，&lt;code&gt;!&lt;/code&gt; 则做 &lt;span&gt;&lt;span&gt;2−x2-x&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;−&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;m &amp;amp;&amp;amp; y = m&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;m || n = m&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;!m = m&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这不是普通布尔代数中随手多塞了一个字母。它让依赖能够表达一个关键约束：内建代码不能依赖只在运行时才加载的模块。例如 &lt;code&gt;FOO=y&lt;/code&gt; 且 &lt;code&gt;BAR=m&lt;/code&gt; 时，&lt;code&gt;FOO depends on BAR&lt;/code&gt; 会把 &lt;code&gt;FOO&lt;/code&gt; 的上限压到 &lt;code&gt;m&lt;/code&gt;，因此 &lt;code&gt;FOO&lt;/code&gt; 不能保持 &lt;code&gt;y&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;code&gt;modules&lt;/code&gt; 属性指定哪一个符号负责开启模块状态。Linux 使用它让大量 &lt;code&gt;tristate&lt;/code&gt; 真正拥有 &lt;code&gt;m&lt;/code&gt;。U-Boot v2026.07 没有 Linux 那样的可加载内核模块体系，也没有定义全局 &lt;code&gt;MODULES&lt;/code&gt; 符号。它的树中仍能看到一些 &lt;code&gt;tristate&lt;/code&gt; 声明，但日常板级裁剪应把 U-Boot 配置理解为主要在 &lt;code&gt;n/y&lt;/code&gt; 之间选择，不要看到 &lt;code&gt;tristate&lt;/code&gt; 就期待生成 &lt;code&gt;.ko&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;depends on&lt;/code&gt; 是上限，不是自动开关&lt;a href=&quot;#depends-on-是上限不是自动开关&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;假设：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config SENSOR&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tristate &quot;Temperature sensor&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;depends on HAS_I2C&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;当 &lt;code&gt;HAS_I2C=n&lt;/code&gt; 时，&lt;code&gt;SENSOR&lt;/code&gt; 的最大值是 &lt;code&gt;n&lt;/code&gt;，界面也会把它隐藏或禁用。当 &lt;code&gt;HAS_I2C=y&lt;/code&gt; 时，Kconfig 只是允许 &lt;code&gt;SENSOR&lt;/code&gt; 取 &lt;code&gt;n&lt;/code&gt;、&lt;code&gt;m&lt;/code&gt; 或 &lt;code&gt;y&lt;/code&gt;，并不会替用户启用它。&lt;/p&gt;&lt;p&gt;所以 &lt;code&gt;depends on&lt;/code&gt; 的读法不是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;选择 SENSOR 时，顺便打开 HAS_I2C。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;而是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;只有 HAS_I2C 足够可用时，SENSOR 才允许达到相应状态。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;菜单也会传递依赖：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;menu &quot;I2C drivers&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;depends on HAS_I2C&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config SENSOR_A&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tristate &quot;Sensor A&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config SENSOR_B&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tristate &quot;Sensor B&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;endmenu&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里 &lt;code&gt;SENSOR_A&lt;/code&gt; 和 &lt;code&gt;SENSOR_B&lt;/code&gt; 都继承 &lt;code&gt;HAS_I2C&lt;/code&gt; 依赖。&lt;code&gt;if ... endif&lt;/code&gt; 也会把表达式附加到内部条目。它们不只是界面折叠工具。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;default&lt;/code&gt; 只在用户没有决定时说话&lt;a href=&quot;#default-只在用户没有决定时说话&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Kconfig 允许一个符号出现多个 &lt;code&gt;default&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config BUFFER_SIZE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;int &quot;Buffer size&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;default 4096 if ARCH_64BIT&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;default 1024&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如果多个默认值的条件都成立，&lt;strong&gt;源码中第一个可见的默认值生效&lt;/strong&gt;。但默认值只在用户没有提供值时使用。用户已经通过配置界面或已有 &lt;code&gt;.config&lt;/code&gt; 选过 &lt;code&gt;2048&lt;/code&gt;，&lt;code&gt;default 4096&lt;/code&gt; 不会每次构建都把它夺回来。&lt;/p&gt;&lt;p&gt;这也解释了为什么新增配置通常保持 &lt;code&gt;default n&lt;/code&gt;。旧配置升级到新源码时，&lt;code&gt;make olddefconfig&lt;/code&gt; 会替新符号采用默认值；如果每个新驱动都 &lt;code&gt;default y&lt;/code&gt;，一次版本升级就会把内核悄悄养胖。Linux 文档只为保留旧行为、纯粹打开下级菜单的 gatekeeper，以及少数用户普遍预期的基础设施等情况建议 &lt;code&gt;default y&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;select&lt;/code&gt; 很方便，也很锋利&lt;a href=&quot;#select-很方便也很锋利&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;实验中的 &lt;code&gt;APP&lt;/code&gt; 写成：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config APP&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool &quot;Sensor application&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;select SENSOR_CORE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config SENSOR_CORE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;depends on HAS_I2C&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我又准备了一份故意不合法的输入：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# CONFIG_SOC_ALPHA is not set&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_APP=y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;此时 &lt;code&gt;HAS_I2C=n&lt;/code&gt;。我把它交给同一个 Linux 6.18 &lt;code&gt;conf&lt;/code&gt;，得到：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;WARNING: unmet direct dependencies detected for SENSOR_CORE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Depends on [n]: HAS_I2C [=n]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Selected by [y]:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- APP [=y]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;select&lt;/code&gt; 强制抬高目标符号的值，&lt;strong&gt;不会沿着目标符号的 &lt;code&gt;depends on&lt;/code&gt; 继续解决依赖&lt;/strong&gt;。于是 &lt;code&gt;APP&lt;/code&gt; 可以把 &lt;code&gt;SENSOR_CORE&lt;/code&gt; 推到 &lt;code&gt;y&lt;/code&gt;，同时留下 &lt;code&gt;HAS_I2C=n&lt;/code&gt; 这个自相矛盾的组合。&lt;/p&gt;&lt;p&gt;这不是求解器忘了帮忙，而是 &lt;code&gt;select&lt;/code&gt; 的定义。Linux 文档明确建议谨慎使用它，最好只选择没有 prompt、也没有依赖的内部符号。若用户应该理解并控制前置条件，使用 &lt;code&gt;depends on&lt;/code&gt;；若只是希望另一个功能通常随当前功能开启，但仍允许用户关闭，考虑 &lt;code&gt;imply&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;一个无聊但安全的重构通常是把能力和实现拆开：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config HAVE_SENSOR_CORE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config SENSOR_CORE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool &quot;Sensor core&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;depends on HAVE_SENSOR_CORE &amp;amp;&amp;amp; HAS_I2C&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;平台可以 &lt;code&gt;select HAVE_SENSOR_CORE&lt;/code&gt;，却不绕过 &lt;code&gt;SENSOR_CORE&lt;/code&gt; 自己的运行条件。多写一个内部符号，比得到一份能通过配置但不能链接的组合便宜得多。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;defconfig&lt;/code&gt;、&lt;code&gt;.config&lt;/code&gt; 到底谁是谁？&lt;a href=&quot;#defconfigconfig-到底谁是谁&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这是我以前最容易混淆的部分。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;Kconfig&lt;/code&gt; 是规则库&lt;a href=&quot;#kconfig-是规则库&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;散布在源码树中的 &lt;code&gt;Kconfig&lt;/code&gt; 文件定义全部符号和关系。Linux 6.18 的 Git 索引中有 1881 个文件名包含 &lt;code&gt;Kconfig&lt;/code&gt;，U-Boot v2026.07 中有 1098 个。单靠一个平铺的配置文件维护这种规模，大概会迅速演化成考古学。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;defconfig&lt;/code&gt; 是最小起点&lt;a href=&quot;#defconfig-是最小起点&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Linux 的架构默认配置通常位于：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;arch/&amp;lt;arch&amp;gt;/configs/&amp;lt;name&amp;gt;_defconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;U-Boot 的板级默认配置位于：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;configs/&amp;lt;board&amp;gt;_defconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它们通常只保存偏离 Kconfig 默认值的决定，不是完整快照。我实际运行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O=/tmp/uboot-out&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sandbox_defconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;U-Boot v2026.07 的 &lt;code&gt;configs/sandbox_defconfig&lt;/code&gt; 中有 392 行已启用的 &lt;code&gt;CONFIG_&lt;/code&gt; 赋值，展开后的 &lt;code&gt;/tmp/uboot-out/.config&lt;/code&gt; 有 1125 行已启用赋值，约为 &lt;strong&gt;2.87 倍&lt;/strong&gt;。剩余值来自默认值、架构选择、依赖和反向依赖。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Important&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;不要把 &lt;code&gt;defconfig&lt;/code&gt; 直接复制成 &lt;code&gt;.config&lt;/code&gt; 后立刻编译，也不要假设它已经列出每个关闭项。应该通过 &lt;code&gt;make &amp;lt;board&amp;gt;_defconfig&lt;/code&gt; 或对应配置目标，让当前源码树重新求解完整配置。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;.config&lt;/code&gt; 是当前构建的完整用户配置&lt;a href=&quot;#config-是当前构建的完整用户配置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;.config&lt;/code&gt; 是某个源码版本、架构和用户选择组合后的工作状态。它比 &lt;code&gt;defconfig&lt;/code&gt; 完整，也更容易在版本升级后变旧。&lt;/p&gt;&lt;p&gt;常见的关闭项写作：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# CONFIG_SENSOR_DEBUG is not set&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这不是普通注释，而是 Kconfig 保存 &lt;code&gt;n&lt;/code&gt; 的标准形式。对于 Makefile 消费的 &lt;code&gt;auto.conf&lt;/code&gt; 和 C 代码消费的 &lt;code&gt;autoconf.h&lt;/code&gt;，关闭符号通常直接缺席。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;savedefconfig&lt;/code&gt; 做反向压缩&lt;a href=&quot;#savedefconfig-做反向压缩&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;完成配置后：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;savedefconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Kconfig 会在源码树或输出树中生成名为 &lt;code&gt;defconfig&lt;/code&gt; 的最小配置，只保留相对当前默认规则有必要记录的选择。U-Boot 新板级配置的常见工作流是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my_board_defconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;menuconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;savedefconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;defconfig&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;configs/my_board_defconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;提交前应重新从保存的 defconfig 展开一次，确认它确实复现预期配置。默认值会变化，所以 &lt;code&gt;savedefconfig&lt;/code&gt; 是相对于&lt;strong&gt;当前 Kconfig 规则&lt;/strong&gt;做的压缩，不是跨所有未来版本的数学最小集。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一次配置命令实际经过什么？&lt;a href=&quot;#一次配置命令实际经过什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;以 U-Boot 为例：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O=build&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my_board_defconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;可以拆成下面几步：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;顶层 Makefile 识别 &lt;code&gt;&amp;lt;board&amp;gt;_defconfig&lt;/code&gt; 配置目标。&lt;/li&gt;
&lt;li&gt;构建主机程序 &lt;code&gt;scripts/kconfig/conf&lt;/code&gt;。这里用的是主机编译器 &lt;code&gt;HOSTCC&lt;/code&gt;，不是给目标板生成代码的交叉编译器。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;conf&lt;/code&gt; 从根 &lt;code&gt;Kconfig&lt;/code&gt; 开始，递归解析 &lt;code&gt;source&lt;/code&gt; 引入的文件，建立符号、菜单和依赖图。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;conf&lt;/code&gt; 读取 &lt;code&gt;configs/my_board_defconfig&lt;/code&gt; 作为输入，补齐默认值并施加约束。&lt;/li&gt;
&lt;li&gt;完整结果写入 &lt;code&gt;build/.config&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;后续同步生成 Makefile 和 C 代码需要的配置文件。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;code&gt;menuconfig&lt;/code&gt;、&lt;code&gt;nconfig&lt;/code&gt;、&lt;code&gt;xconfig&lt;/code&gt; 和 &lt;code&gt;gconfig&lt;/code&gt; 共享同一套解析与求值核心，只是前端不同：&lt;/p&gt;
















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;目标&lt;/th&gt;&lt;th&gt;交互方式&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;config&lt;/code&gt;&lt;/td&gt;&lt;td&gt;逐行询问&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;menuconfig&lt;/code&gt;&lt;/td&gt;&lt;td&gt;ncurses 菜单&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;nconfig&lt;/code&gt;&lt;/td&gt;&lt;td&gt;另一套 ncurses 界面&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;xconfig&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Qt 图形界面&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;oldconfig&lt;/code&gt;&lt;/td&gt;&lt;td&gt;读取旧配置，并逐个询问新符号&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;olddefconfig&lt;/code&gt;&lt;/td&gt;&lt;td&gt;读取旧配置，新符号直接采用默认值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;alldefconfig&lt;/code&gt;&lt;/td&gt;&lt;td&gt;从所有默认值生成新配置&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;allnoconfig&lt;/code&gt;&lt;/td&gt;&lt;td&gt;尽量全部设为 &lt;code&gt;n&lt;/code&gt;，仍受强制关系约束&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;savedefconfig&lt;/code&gt;&lt;/td&gt;&lt;td&gt;把当前配置压缩成最小 defconfig&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;listnewconfig&lt;/code&gt;&lt;/td&gt;&lt;td&gt;列出旧配置中没有的新符号&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;code&gt;menuconfig&lt;/code&gt; 并不拥有特殊的求值能力。它只是让人类更容易编辑同一张配置图。自动化构建通常更偏爱 &lt;code&gt;olddefconfig&lt;/code&gt;，因为 CI 不太擅长在蓝色菜单里按空格键（至少目前如此）。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;配置怎样进入 Makefile？&lt;a href=&quot;#配置怎样进入-makefile&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;同步配置后，Linux 和 U-Boot 都会生成：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;include/config/auto.conf&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;内容类似：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_HAS_I2C&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_LOG_LEVEL&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CONFIG_SENSOR&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;顶层构建系统把它当作 Makefile 片段包含进来。子目录 Makefile 就可以写：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;CONFIG_SENSOR&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; sensor.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;展开过程非常直接：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CONFIG_SENSOR=y&lt;/code&gt; 得到 &lt;code&gt;obj-y += sensor.o&lt;/code&gt;，对象内建。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CONFIG_SENSOR=m&lt;/code&gt; 得到 &lt;code&gt;obj-m += sensor.o&lt;/code&gt;，Linux 把对象送入模块构建路径。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CONFIG_SENSOR&lt;/code&gt; 未定义时得到 &lt;code&gt;obj- += sensor.o&lt;/code&gt;，正常的 &lt;code&gt;obj-y&lt;/code&gt; / &lt;code&gt;obj-m&lt;/code&gt; 收集逻辑不会选择它。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;目录也能用同样方式裁剪：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;CONFIG_HAS_I2C&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; i2c/&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;所以关闭一个驱动最彻底的效果不是给整份 C 文件套上 &lt;code&gt;#if 0&lt;/code&gt;，而是让它根本不进入编译输入。编译器少工作，链接器也不必期待稍后用 section garbage collection 擦屁股。&lt;/p&gt;&lt;p&gt;U-Boot 还要处理 SPL、TPL、VPL 等不同阶段，因此源码中常见：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;CONFIG_&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;PHASE_&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;ADC&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; adc-uclass.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;PHASE_&lt;/code&gt; 会让构建阶段选择 &lt;code&gt;CONFIG_ADC&lt;/code&gt;、&lt;code&gt;CONFIG_SPL_ADC&lt;/code&gt;、&lt;code&gt;CONFIG_TPL_ADC&lt;/code&gt; 等对应符号。裁剪 U-Boot 时必须问清楚一个功能属于完整 U-Boot 还是早期阶段；只关 &lt;code&gt;CONFIG_FOO&lt;/code&gt;，不代表 &lt;code&gt;CONFIG_SPL_FOO&lt;/code&gt; 自动消失。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;Linux 中谁消费 &lt;code&gt;obj-y&lt;/code&gt; 和 &lt;code&gt;obj-m&lt;/code&gt;？&lt;a href=&quot;#linux-中谁消费-obj-y-和-obj-m&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;上面的表达式还留下了一个问题：谁读取这些变量并把它们变成编译动作？在 Linux 6.18 中，关键入口叫 &lt;code&gt;scripts/Makefile.build&lt;/code&gt;。&lt;code&gt;scripts/Kbuild.include&lt;/code&gt; 先定义一段缩写：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;build&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; -f &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;srctree&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/scripts/Makefile.build obj&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;因此顶层 Makefile 中的：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;Q&lt;/span&gt;&lt;span&gt;)$(&lt;/span&gt;&lt;span&gt;MAKE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;build&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;=drivers/i2c&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;大致等价于再次调用 Make，并指定当前处理的目录：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scripts/Makefile.build&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;obj=drivers/i2c&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;scripts/Makefile.build&lt;/code&gt; 是逐目录工作的通用引擎。它先清空 &lt;code&gt;obj-y&lt;/code&gt;、&lt;code&gt;obj-m&lt;/code&gt;、&lt;code&gt;lib-y&lt;/code&gt;、编译 flags 等变量，避免环境中的同名变量混进来；然后读取 &lt;code&gt;include/config/auto.conf&lt;/code&gt;、公共 Kbuild 规则、编译器规则、当前目录的 &lt;code&gt;Kbuild&lt;/code&gt; 或 &lt;code&gt;Makefile&lt;/code&gt;，最后再读取 &lt;code&gt;scripts/Makefile.lib&lt;/code&gt;。如果同一目录同时存在 &lt;code&gt;Kbuild&lt;/code&gt; 和 &lt;code&gt;Makefile&lt;/code&gt;，Kbuild 基础设施优先使用 &lt;code&gt;Kbuild&lt;/code&gt;。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;顶层 Makefile&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;make build=目录&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Makefile.build&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;当前目录 Kbuild 或 Makefile&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;obj-y 与 obj-m&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;built-in.a 与 modules.order&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;顶层 Makefile&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;make build=目录&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;scripts/Makefile.build&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;当前目录 Kbuild 或 Makefile&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;obj-y 与 obj-m&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;built-in.a 与 modules.order&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;读取当前目录规则后，&lt;code&gt;scripts/Makefile.build&lt;/code&gt; 会展开复合对象，找出要递归进入的子目录，并把 &lt;code&gt;obj-y&lt;/code&gt; 中的子目录映射为对应的 &lt;code&gt;built-in.a&lt;/code&gt;。启用模块构建时，它也会沿 &lt;code&gt;obj-m&lt;/code&gt; 收集模块目标并生成 &lt;code&gt;modules.order&lt;/code&gt;。例如 &lt;code&gt;drivers/i2c/&lt;/code&gt; 不是由顶层 Makefile 硬编码每一个源文件，而是同一套引擎带着不同的 &lt;code&gt;obj&lt;/code&gt; 参数逐层下钻。&lt;/p&gt;&lt;p&gt;这就是本文需要抵达的 Kbuild 边界：Kconfig 求出 &lt;code&gt;CONFIG_FOO&lt;/code&gt;，&lt;code&gt;auto.conf&lt;/code&gt; 把值交给 Make，目录中的 Kbuild 文件把它展开成 &lt;code&gt;obj-y&lt;/code&gt; 或 &lt;code&gt;obj-m&lt;/code&gt;，&lt;code&gt;scripts/Makefile.build&lt;/code&gt; 再把列表变成当前目录的编译和归档目标。至于 &lt;code&gt;.c&lt;/code&gt; 如何变成 &lt;code&gt;.o&lt;/code&gt;、多文件模块如何合并、&lt;code&gt;modpost&lt;/code&gt; 怎样生成模块元数据，以及 &lt;code&gt;built-in.a&lt;/code&gt; 最后如何进入 &lt;code&gt;vmlinux&lt;/code&gt;，那已经是另一条足够写满一篇文章的数据流。&lt;/p&gt;&lt;p&gt;U-Boot 也继承了 Kbuild 风格，并维护自己的 &lt;code&gt;scripts/Makefile.build&lt;/code&gt;，但它的镜像阶段、SPL/TPL/VPL 和兼容配置让后续链接路径与 Linux 不完全相同。这里借 Linux 6.18 看清通用接口，不把两套最终链接过程强行说成一回事。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;配置怎样进入 C 代码？&lt;a href=&quot;#配置怎样进入-c-代码&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;另一份关键生成物是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;include/generated/autoconf.h&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我的实验配置生成了：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#define&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CONFIG_LOG_LEVEL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#define&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CONFIG_HAS_I2C&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#define&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CONFIG_APP&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#define&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CONFIG_MODULES&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#define&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CONFIG_SOC_ALPHA&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#define&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CONFIG_SENSOR_CORE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#define&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CONFIG_SENSOR&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;C 代码因此可以做更细粒度的选择：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IS_ENABLED&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CONFIG_SENSOR&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;register_sensor&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Linux 中优先使用项目提供的 &lt;code&gt;IS_ENABLED()&lt;/code&gt;、&lt;code&gt;IS_BUILTIN()&lt;/code&gt;、&lt;code&gt;IS_MODULE()&lt;/code&gt; 等宏，而不是把所有判断都写成裸 &lt;code&gt;#ifdef&lt;/code&gt;。原因之一是模块状态：&lt;code&gt;CONFIG_SENSOR=m&lt;/code&gt; 通常表现为 &lt;code&gt;CONFIG_SENSOR_MODULE=1&lt;/code&gt;，并不等同于 &lt;code&gt;#define CONFIG_SENSOR 1&lt;/code&gt;。&lt;code&gt;IS_ENABLED(CONFIG_SENSOR)&lt;/code&gt; 能同时覆盖内建和模块两种启用状态。&lt;/p&gt;&lt;p&gt;对于数值和字符串，生成结果保留相应值：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#define&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CONFIG_LOG_LEVEL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#define&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CONFIG_LOCALVERSION&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;-lab&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;不要手改 &lt;code&gt;include/config/auto.conf&lt;/code&gt; 或 &lt;code&gt;include/generated/autoconf.h&lt;/code&gt;。它们是派生文件，下一次同步就会重写。要改变配置，修改 Kconfig、defconfig 或通过受支持的配置前端修改 &lt;code&gt;.config&lt;/code&gt;。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Linux 6.18 已把 &lt;code&gt;syncconfig&lt;/code&gt; 标为内部实现细节。正常用户运行 &lt;code&gt;make&lt;/code&gt; 或配置目标即可，顶层 Makefile 会在 &lt;code&gt;.config&lt;/code&gt; 或 Kconfig 输入变化时更新这些生成物。手工调用 &lt;code&gt;make syncconfig&lt;/code&gt; 通常是在绕过正确入口。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;U-Boot 为什么看起来还多一层？&lt;a href=&quot;#u-boot-为什么看起来还多一层&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;U-Boot 从 v2014.10-rc1 开始用 Kconfig 替代旧的板级配置体系，但 v2026.07 仍保留一些兼容生成步骤。除了与 Linux 相同的 &lt;code&gt;.config&lt;/code&gt;、&lt;code&gt;include/config/auto.conf&lt;/code&gt; 和 &lt;code&gt;include/generated/autoconf.h&lt;/code&gt;，它还可能生成或使用：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;include/config.h&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;include/autoconf.mk&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;spl/include/autoconf.mk&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;tpl/include/autoconf.mk&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;板级代码中也仍能看到 &lt;code&gt;include/configs/&amp;lt;board&amp;gt;.h&lt;/code&gt;。因此在 U-Boot 中遇到一个 &lt;code&gt;CONFIG_&lt;/code&gt; 或 &lt;code&gt;CFG_&lt;/code&gt; 宏时，不能只在 Kconfig 文件里搜索一次就宣布它不存在。要区分：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Kconfig 管理并生成的 &lt;code&gt;CONFIG_*&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;旧板级头文件或其他构建步骤提供的兼容配置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CFG_*&lt;/code&gt; 这类不一定由 Kconfig 管理的编译期常量。&lt;/li&gt;
&lt;li&gt;Device Tree 描述的硬件实例与运行时数据。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;新配置应优先进入 Kconfig，而不是继续往板级头文件塞宏。旧路径存在是迁移历史，不是新代码需要复制的设计模式。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;裁剪一个真实功能时，我现在怎么做？&lt;a href=&quot;#裁剪一个真实功能时我现在怎么做&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;假设我要在 U-Boot 中去掉某个以太网驱动，或者在 Linux 中加入某个 I2C 控制器。我会沿着同一条链倒着走。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;1. 从符号定义开始&lt;a href=&quot;#1-从符号定义开始&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在 &lt;code&gt;menuconfig&lt;/code&gt; 中按 &lt;code&gt;/&lt;/code&gt; 搜索符号名，查看：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;定义位置。&lt;/li&gt;
&lt;li&gt;类型和 prompt。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;depends on&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;被哪些符号 &lt;code&gt;select&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;当前值为何被限制。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果界面显示：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Symbol: FOO [=n]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Depends on: BAR [=n] &amp;amp;&amp;amp; ARCH_BAZ [=y]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;真正阻止 &lt;code&gt;FOO&lt;/code&gt; 的是 &lt;code&gt;BAR=n&lt;/code&gt;。反复按空格不会改变这个上限。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. 查 Kbuild/Makefile 消费点&lt;a href=&quot;#2-查-kbuildmakefile-消费点&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;寻找类似：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;obj-&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;CONFIG_FOO&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; foo.o&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;确认符号控制的是单个对象、整个目录，还是一组公共代码。有时 UI 中一个开关只控制上层框架，具体驱动还有第二个符号。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3. 查 C 代码中的条件&lt;a href=&quot;#3-查-c-代码中的条件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;查看 &lt;code&gt;IS_ENABLED(CONFIG_FOO)&lt;/code&gt;、&lt;code&gt;#ifdef CONFIG_FOO&lt;/code&gt; 和该符号影响的数据结构。一个符号可能既控制对象是否编译，也控制已编译文件中的某段代码。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4. 检查阶段和架构变体&lt;a href=&quot;#4-检查阶段和架构变体&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;U-Boot 要额外查看 &lt;code&gt;CONFIG_SPL_FOO&lt;/code&gt;、&lt;code&gt;CONFIG_TPL_FOO&lt;/code&gt; 和 &lt;code&gt;CONFIG_VPL_FOO&lt;/code&gt;。Linux 则要留意 &lt;code&gt;FOO=y&lt;/code&gt; 与 &lt;code&gt;FOO=m&lt;/code&gt; 的差别，以及目标架构的 Kconfig 是否为它追加了依赖或默认值。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;5. 从干净默认配置重放&lt;a href=&quot;#5-从干净默认配置重放&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;U-Boot：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O=build&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my_board_defconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O=build&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;menuconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O=build&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;savedefconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Linux：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O=build&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ARCH=arm64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my_defconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O=build&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ARCH=arm64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;menuconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;O=build&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ARCH=arm64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;savedefconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;交叉编译时再为实际构建设置正确的 &lt;code&gt;CROSS_COMPILE&lt;/code&gt;。&lt;code&gt;O=&lt;/code&gt;、&lt;code&gt;ARCH&lt;/code&gt; 和 &lt;code&gt;CROSS_COMPILE&lt;/code&gt; 应在同一输出目录的配置与构建过程中保持一致，避免拿一份架构 A 的 &lt;code&gt;.config&lt;/code&gt; 去喂架构 B。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;6. 比较语义差异，不比较整份噪声&lt;a href=&quot;#6-比较语义差异不比较整份噪声&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Linux 提供：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;scripts/diffconfig&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;old.config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new.config&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它比直接 &lt;code&gt;diff&lt;/code&gt; 两份完整 &lt;code&gt;.config&lt;/code&gt; 更适合查看符号变化。配置升级时还可以使用：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;listnewconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;olddefconfig&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;前者告诉我出现了哪些新问题，后者按默认值回答它们。需要审查安全、启动或尺寸影响时，我会先看 &lt;code&gt;listnewconfig&lt;/code&gt;，而不是让新符号在日志里无声飘过。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;几个容易制造幽灵问题的操作&lt;a href=&quot;#几个容易制造幽灵问题的操作&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;直接编辑 &lt;code&gt;.config&lt;/code&gt; 后不重新求解&lt;a href=&quot;#直接编辑-config-后不重新求解&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;手改 &lt;code&gt;.config&lt;/code&gt; 可以用于快速实验，但输入仍受依赖约束。应随后运行 &lt;code&gt;olddefconfig&lt;/code&gt; 或受支持的配置目标。否则文件里写着 &lt;code&gt;y&lt;/code&gt;，求值结果、生成头和实际构建可能并不支持这个愿望。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;把 &lt;code&gt;select&lt;/code&gt; 当作递归安装依赖&lt;a href=&quot;#把-select-当作递归安装依赖&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;它不会递归满足目标的依赖。警告已经很直白，修 Kconfig 关系，不要忽视警告。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;在 defconfig 中记录所有默认值&lt;a href=&quot;#在-defconfig-中记录所有默认值&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这样得到的是一份巨大快照。默认值改变时，它会把旧决定钉死，也让代码审查看不出板级真正选择了什么。让 &lt;code&gt;savedefconfig&lt;/code&gt; 做压缩。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;只看 &lt;code&gt;menuconfig&lt;/code&gt; 中是否可见&lt;a href=&quot;#只看-menuconfig-中是否可见&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;无 prompt 的内部符号不会出现；依赖为 &lt;code&gt;n&lt;/code&gt; 的条目也可能隐藏。搜索符号并查看定义，比滚动菜单可靠。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;认为 Kconfig 会自动裁剪任何东西&lt;a href=&quot;#认为-kconfig-会自动裁剪任何东西&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;如果新增了：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;config FOO&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bool &quot;Foo support&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;却没有在 Makefile 或 C 代码中消费 &lt;code&gt;CONFIG_FOO&lt;/code&gt;，切换它不会改变二进制的一个 bit。Kconfig 只产生决定，构建规则和源码必须执行决定。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我最终保留的心智模型&lt;a href=&quot;#我最终保留的心智模型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我现在把 Kconfig 看成一个有 4 类边的符号图：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;depends on&lt;/code&gt; 压低取值上限。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;select&lt;/code&gt; 强制抬高取值下限，但不替目标检查依赖。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;imply&lt;/code&gt; 提供可被用户拒绝的弱下限。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;default&lt;/code&gt; 只在用户没有作出决定时给出起点。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;code&gt;defconfig&lt;/code&gt; 是送进图中的稀疏输入，&lt;code&gt;.config&lt;/code&gt; 是当前版本求解后的工作状态，&lt;code&gt;auto.conf&lt;/code&gt; 和 &lt;code&gt;autoconf.h&lt;/code&gt; 是分别交给 Make 与 C 的机器接口。随后 &lt;code&gt;obj-$(CONFIG_FOO)&lt;/code&gt; 决定文件是否进场，&lt;code&gt;IS_ENABLED(CONFIG_FOO)&lt;/code&gt; 决定文件内部哪段代码存活。&lt;/p&gt;&lt;p&gt;这套设计从 1881 份 Linux Kconfig 文件扩展到 U-Boot 的 1098 份，靠的不是某个神奇菜单，而是把配置声明、求值、构建选择和源码条件分开。未来我怀疑更多固件会把硬件描述继续推向 Device Tree，把编译期能力留给 Kconfig；但只要我们仍要为固定板卡在镜像尺寸、启动链和驱动集合之间做取舍，这张符号图就不会消失。&lt;/p&gt;&lt;p&gt;接下来最好的练习不是继续读语法表。选自己板子的一个驱动，从它的 &lt;code&gt;config FOO&lt;/code&gt; 追到 &lt;code&gt;obj-$(CONFIG_FOO)&lt;/code&gt;，再追到 &lt;code&gt;autoconf.h&lt;/code&gt; 和最终镜像。追完一个真实符号，蓝色菜单就只是 UI 了。很好，我终于可以回去裁内核，而不是在 &lt;code&gt;menuconfig&lt;/code&gt; 里用空格键进行嵌入式占卜。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/Documentation/kbuild/kconfig-language.rst&quot; target=&quot;_blank&quot;&gt;Linux 6.18 Kconfig Language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/Documentation/kbuild/kconfig.rst&quot; target=&quot;_blank&quot;&gt;Linux 6.18 Configuration targets and editors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/scripts/kconfig/Makefile&quot; target=&quot;_blank&quot;&gt;Linux 6.18 scripts/kconfig/Makefile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/scripts/Makefile.build&quot; target=&quot;_blank&quot;&gt;Linux 6.18 scripts/Makefile.build&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/blob/v6.18/Documentation/kbuild/makefiles.rst&quot; target=&quot;_blank&quot;&gt;Linux 6.18 Linux Kernel Makefiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kernel.org/releases.html&quot; target=&quot;_blank&quot;&gt;Linux Kernel active releases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.u-boot.org/en/v2026.07/develop/kconfig.html&quot; target=&quot;_blank&quot;&gt;U-Boot v2026.07: Kconfig in U-Boot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.u-boot.org/en/v2026.07/build/gcc.html&quot; target=&quot;_blank&quot;&gt;U-Boot v2026.07: Building with GCC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded></item><item><title>从错误码到栈展开: 我如何理解 C++ 异常</title><link>https://bfmhno3.github.io/posts/cpp-exception/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/cpp-exception/</guid><description>我从嵌入式 C 的错误码出发，用一个可运行的栈展开实验串起 C++ 异常、RAII、捕获边界、noexcept、异常安全，以及 std::expected 的适用边界。</description><pubDate>Tue, 01 Sep 2026 09:39:28 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;“异常不是一种错误返回值。它是一种非局部的控制流。”&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;我以前写嵌入式 C 时，错误处理基本是一条熟悉的流水线：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;read_sensor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (result &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; result;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;函数返回错误码，调用者检查错误码，然后继续向上传递。一路上每层都写一次 &lt;code&gt;if&lt;/code&gt;，最终总有某一层真正处理它 - 或者某一层忘记检查，程序带着错误继续运行。后者当然非常有趣，只是通常不会在你方便调试的时候发生。&lt;/p&gt;
&lt;p&gt;C++ 异常做的事情，本质上是把”逐层手动返回错误”改成了”自动向上寻找能够处理错误的位置”。&lt;/p&gt;
&lt;p&gt;但这句话里最关键的不是”自动”，而是”能够处理”。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;异常最基本的语法&lt;a href=&quot;#异常最基本的语法&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;抛出异常使用 &lt;code&gt;throw&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;divide&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;invalid_argument&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;division by zero&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;调用者使用 &lt;code&gt;try&lt;/code&gt; 和 &lt;code&gt;catch&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;divide&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;invalid_argument&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cerr&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;参数错误: &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;what&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;几个基本规则：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 可能抛出异常的代码&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SpecificException&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 处理特定异常&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;exception&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 处理其他标准异常&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (...) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 处理所有其他异常&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;捕获顺序&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;code&gt;catch&lt;/code&gt; 按从上到下的顺序匹配，因此具体类型应放在前面，基类放在后面；&lt;code&gt;catch (...)&lt;/code&gt; 只能放在最后。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;异常一般按值抛出、按 &lt;code&gt;const&lt;/code&gt; 引用捕获：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyException&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;motor stalled&quot;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyException&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;不要按值捕获：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (std::&lt;/span&gt;&lt;span&gt;exception&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;span&gt;  // 不推荐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这可能发生对象切片，丢失派生异常中的信息。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;抛出以后发生了什么？&lt;a href=&quot;#抛出以后发生了什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;假设调用链是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;调用&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;调用&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;调用&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;main()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;initialize_system()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;load_configuration()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;read_file()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;调用&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;调用&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;调用&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;main()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;initialize_system()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;load_configuration()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;read_file()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;如果 &lt;code&gt;read_file()&lt;/code&gt; 抛出异常，它不会像错误码那样正常返回。C++ 会沿调用栈向上寻找匹配的 &lt;code&gt;catch&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;开始栈展开&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;继续传播&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;找到匹配的处理器&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;read_file(): 抛出异常&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;退出 load_configuration()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;退出 initialize_system()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;main(): catch&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;开始栈展开&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;继续传播&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;找到匹配的处理器&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;read_file(): 抛出异常&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;退出 load_configuration()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;退出 initialize_system()&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;main(): catch&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这个过程叫做栈展开，stack unwinding。&lt;/p&gt;&lt;p&gt;在展开过程中，已经构造完成的局部对象会被自动销毁：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;lock_guard&lt;/span&gt;&lt;span&gt;&amp;lt;std::&lt;/span&gt;&lt;span&gt;mutex&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;lock&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mutex&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;perform_operation&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt;  // 抛出异常&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;即使 &lt;code&gt;perform_operation()&lt;/code&gt; 抛出异常：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lock&lt;/code&gt; 的析构函数仍然执行，互斥锁被释放；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data&lt;/code&gt; 的析构函数仍然执行，内存被释放。&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;RAII 是异常安全的地基&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;资源放进对象里，清理由析构函数负责，异常只负责改变控制流。不要让 &lt;code&gt;catch&lt;/code&gt; 承担本应属于对象生命周期的清理工作。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;section&gt;&lt;h3&gt;我跑了一个最小的栈展开实验&lt;a href=&quot;#我跑了一个最小的栈展开实验&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;只看规则还是有点抽象，所以我写了一个 34 行的小程序，让 3 个局部对象把构造和析构顺序打印出来，然后在最深处抛出 &lt;code&gt;std::runtime_error&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;stdexcept&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;string_view&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Trace&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;explicit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Trace&lt;/span&gt;&lt;span&gt;(std::&lt;/span&gt;&lt;span&gt;string_view&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name_&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;construct &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name_&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;~Trace&lt;/span&gt;&lt;span&gt;() { std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;destroy &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name_&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;private:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string_view&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name_&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;read_file&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;Trace&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;trace&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;read_file&quot;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;runtime_error&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;disk error&quot;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;load_configuration&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;Trace&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;trace&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;load_configuration&quot;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;read_file&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Trace&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;trace&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;main&quot;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;load_configuration&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;runtime_error&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;catch runtime_error: &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;what&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;我用 GCC 15.3 和 &lt;code&gt;-std=c++23 -Wall -Wextra -Wpedantic -Werror&lt;/code&gt; 编译，实际输出是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;construct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;construct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;load_configuration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;construct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;read_file&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;destroy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;read_file&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;destroy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;load_configuration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;destroy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;runtime_error:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;disk&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;3 个对象按构造的严格逆序销毁，最后才进入 1 个匹配的处理器。异常像按下了调用栈的倒带键：控制流向上走，析构函数沿路把资源收回来。若把同一条 3 层调用链改成 C 风格错误码，每一层都要检查并继续返回；这里中间两层没有错误转发代码，只保留了真正处理错误的边界。&lt;/p&gt;&lt;p&gt;因此，现代 C++ 中通常不应该这样清理资源：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Resource&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;resource&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;acquire&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;resource&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (...) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;release&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;resource&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;release&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;resource&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;应该把资源包装成对象：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;resource&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;make_resource&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;resource&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;资源对象的析构函数负责清理。这样正常返回、提前返回和抛出异常都走同一套清理逻辑。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;throw;&lt;/code&gt; 和 &lt;code&gt;throw e;&lt;/code&gt; 不一样&lt;a href=&quot;#throw-和-throw-e-不一样&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;捕获后重新抛出当前异常：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;exception&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;log_error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;what&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里应当使用没有参数的 &lt;code&gt;throw;&lt;/code&gt;。它重新抛出当前正在处理的异常，保留原始异常对象及其动态类型。&lt;/p&gt;&lt;p&gt;不要写：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;exception&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这会用表达式 &lt;code&gt;e&lt;/code&gt; 的静态类型 &lt;code&gt;std::exception&lt;/code&gt; 初始化一个新的异常对象，因此派生部分会被切掉。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;自定义异常&lt;a href=&quot;#自定义异常&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;自定义异常通常继承 &lt;code&gt;std::runtime_error&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConfigurationError&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;runtime_error&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; std::runtime_error::&lt;/span&gt;&lt;span&gt;runtime_error&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;使用：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;load_configuration&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;configuration_exists&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConfigurationError&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;configuration file is missing&quot;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;捕获：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConfigurationError&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cerr&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;配置错误: &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;what&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;不要为每一种小错误都创建一个异常类。异常类型的价值在于让调用者能够采取不同措施，而不仅仅是让类名看起来很完整。&lt;/p&gt;&lt;p&gt;如果两个错误的处理策略完全一样，它们很可能不需要两个异常类型。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;到底什么时候应该抛异常？&lt;a href=&quot;#到底什么时候应该抛异常&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我认为最有用的判断标准是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;判断是否应该抛出&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;当前函数无法完成它承诺的操作，而且调用者不太可能在紧邻的位置用普通分支处理这个失败，此时异常通常是自然的接口。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;例如：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Configuration&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;load_configuration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::filesystem::&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这个函数承诺返回一个有效配置。如果文件损坏，函数无法兑现承诺。此时抛异常很自然：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Configuration&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;load_configuration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::filesystem::&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;ifstream&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConfigurationError&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;cannot open configuration: &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;()};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;parse_configuration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;特别适合异常的情况包括：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;构造函数无法建立对象不变量；&lt;/li&gt;
&lt;li&gt;初始化过程失败，程序无法继续；&lt;/li&gt;
&lt;li&gt;文件、数据库或系统资源出现罕见故障；&lt;/li&gt;
&lt;li&gt;某个深层函数无法处理错误，需要跨越多层调用栈；&lt;/li&gt;
&lt;li&gt;函数通常成功，而失败属于异常路径。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;构造函数尤其适合异常，因为构造函数没有返回值可以装错误码：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SerialPort&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;explicit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SerialPort&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;device&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 约定 open_device 成功时返回空 error_code。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;error_code&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;open_device&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;device&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;system_error&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                              &lt;/span&gt;&lt;span&gt;&quot;cannot open serial port &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;device&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;要么构造出一个有效的 &lt;code&gt;SerialPort&lt;/code&gt;，要么构造失败并抛出。不会产生一个”半死不活、需要调用 &lt;code&gt;is_valid()&lt;/code&gt; 才敢使用”的对象。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;什么情况不应该抛异常？&lt;a href=&quot;#什么情况不应该抛异常&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;异常不适合表达正常、频繁、可预期的业务分支。&lt;/p&gt;&lt;p&gt;例如查找元素：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;find_user&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UserId&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;“没有找到用户”可能完全正常，因此返回空指针或 &lt;code&gt;std::optional&lt;/code&gt; 更合适：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;optional&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;find_user&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UserId&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;类似情况还有：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;队列当前为空；&lt;/li&gt;
&lt;li&gt;非阻塞读取暂时没有数据；&lt;/li&gt;
&lt;li&gt;用户输入不符合格式；&lt;/li&gt;
&lt;li&gt;网络操作需要重试；&lt;/li&gt;
&lt;li&gt;传感器偶尔超时，而超时是协议的一部分；&lt;/li&gt;
&lt;li&gt;解析器试探某种语法但没有匹配；&lt;/li&gt;
&lt;li&gt;高频控制循环中的状态变化。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;在 C++23 中，还可以使用 &lt;code&gt;std::expected&amp;lt;T, E&amp;gt;&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;enum&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SensorError&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;kTimeout&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;kDisconnected&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;kChecksumError&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;expected&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Reading&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;SensorError&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;read_sensor&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;调用者显式处理：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;read_sensor&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; SensorError::&lt;/span&gt;&lt;span&gt;kTimeout&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;retry&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我通常这样区分：&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;情况&lt;/th&gt;&lt;th&gt;更合适的机制&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;调用者经常立即处理失败&lt;/td&gt;&lt;td&gt;&lt;code&gt;optional&lt;/code&gt;、&lt;code&gt;expected&lt;/code&gt;、错误码&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;失败罕见，需要跨越多层调用&lt;/td&gt;&lt;td&gt;异常&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;硬实时路径，需要可预测延迟&lt;/td&gt;&lt;td&gt;错误码或 &lt;code&gt;expected&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;构造函数无法建立有效对象&lt;/td&gt;&lt;td&gt;异常&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;查询”有没有”&lt;/td&gt;&lt;td&gt;&lt;code&gt;optional&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;可恢复的输入错误或状态不匹配&lt;/td&gt;&lt;td&gt;&lt;code&gt;expected&lt;/code&gt;、错误码&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;编程错误或不变量被破坏&lt;/td&gt;&lt;td&gt;断言、契约检查或终止&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;内存不足、文件系统故障等罕见故障&lt;/td&gt;&lt;td&gt;通常可以使用异常&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;异常不是”更高级的错误码”。它们适合不同形状的控制流。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;应该在哪里捕获？&lt;a href=&quot;#应该在哪里捕获&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这是异常设计中最重要的问题。答案不是”尽量靠近抛出点”，而是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;在能够采取有意义行动的层级捕获。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;所谓有意义行动，大致只有几种：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;重试操作；&lt;/li&gt;
&lt;li&gt;使用备用方案；&lt;/li&gt;
&lt;li&gt;请求用户提供新的输入；&lt;/li&gt;
&lt;li&gt;把底层错误翻译成当前层的领域错误；&lt;/li&gt;
&lt;li&gt;回滚当前事务；&lt;/li&gt;
&lt;li&gt;结束当前请求，但保持整个服务继续运行；&lt;/li&gt;
&lt;li&gt;在程序最外层记录错误并安全退出。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果某一层什么都做不了，就不应该为了”看起来处理了错误”而捕获。&lt;/p&gt;&lt;p&gt;这种代码几乎没有价值：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;load_configuration&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;exception&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它只是把异常接住，然后原样扔回去。除非这里需要补充上下文，否则让异常自然传播即可。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;一个实际的分层例子&lt;a href=&quot;#一个实际的分层例子&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;read_file&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::filesystem::&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Configuration&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;load_configuration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::filesystem::&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;initialize_application&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;底层文件函数报告它真正知道的事情：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;read_file&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::filesystem::&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;ifstream&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 不从 errno 猜测 iostream 失败的原因。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; std::ios_base::&lt;/span&gt;&lt;span&gt;failure&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;cannot open &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;()};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {std::&lt;/span&gt;&lt;span&gt;istreambuf_iterator&lt;/span&gt;&lt;span&gt;&amp;lt;char&amp;gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;istreambuf_iterator&lt;/span&gt;&lt;span&gt;&amp;lt;char&amp;gt;&lt;/span&gt;&lt;span&gt;{}};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;配置层知道这个文件对业务意味着什么，因此可以翻译异常：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Configuration&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;load_configuration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::filesystem::&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;parse_configuration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;read_file&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;system_error&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;throw_with_nested&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ConfigurationError&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;failed to load application configuration&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;最外层决定如何向用户呈现：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;load_configuration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;app.conf&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;run_application&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EXIT_SUCCESS&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConfigurationError&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cerr&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;程序无法启动: &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;what&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EXIT_FAILURE&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;exception&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cerr&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;未处理的错误: &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;what&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EXIT_FAILURE&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;注意这里的职责分工：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;底层发现并描述技术故障；&lt;/li&gt;
&lt;li&gt;中间层在必要时翻译语义；&lt;/li&gt;
&lt;li&gt;顶层决定程序是重试、降级还是退出。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这有点像消防报警：传感器负责报告烟雾，楼层控制器负责判断区域，最终由消防系统决定疏散。让烟雾传感器自己决定是否撤离整栋楼，职责多少有点超纲。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;捕获异常不等于”解决异常”&lt;a href=&quot;#捕获异常不等于解决异常&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这是一个非常常见的困惑。&lt;/p&gt;&lt;p&gt;假设串口打开失败：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SerialPortError&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;COM3 is unavailable&quot;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;捕获以后，你不一定能”修好 COM3”。处理异常可能只是：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;尝试另一个串口；&lt;/li&gt;
&lt;li&gt;等待一秒后重试；&lt;/li&gt;
&lt;li&gt;提示用户检查连接；&lt;/li&gt;
&lt;li&gt;禁用相关功能；&lt;/li&gt;
&lt;li&gt;记录错误并结束程序。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;“处理”指的是把系统带到一个定义良好的状态，而不是必须让原操作成功。&lt;/p&gt;&lt;p&gt;如果当前层做不到这一点，就继续传播。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;try&lt;/code&gt; 块中的代码是不是应该尽量少？&lt;a href=&quot;#try-块中的代码是不是应该尽量少&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;不是因为 &lt;code&gt;try&lt;/code&gt; 天生按行收费，而是因为语义。&lt;/p&gt;&lt;p&gt;在 GCC、Clang 和 MSVC 常见的表驱动异常实现中，未抛出异常的正常路径通常不需要像错误码那样在每一层增加显式分支。代价主要转移到了异常处理元数据、二进制体积和真正抛出时的工作；这是一种常见实现策略，不是 C++ 标准给出的性能保证。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;性能模型&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;没有抛出&lt;/strong&gt;：常见表驱动实现中的路径成本通常很低，但异常处理元数据和二进制体积并非免费。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;真正抛出&lt;/strong&gt;：运行时需要构造异常对象、查找处理器、展开调用栈并执行析构函数，成本较高且延迟不容易预测。&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;try&lt;/code&gt; 里面有十行还是一百行，并不是主要性能因素。&lt;/p&gt;&lt;p&gt;不过，过大的 &lt;code&gt;try&lt;/code&gt; 块会造成另一个问题：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;load_config&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;connect_database&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;start_network&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;initialize_ui&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;exception&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 到底哪一步失败了？&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里捕获得太宽，错误上下文容易变模糊。可以让各层的异常信息足够明确，或者按恢复策略划分边界。&lt;/p&gt;&lt;p&gt;所以更准确的原则是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;划定 &lt;code&gt;try&lt;/code&gt; 的边界&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;code&gt;try&lt;/code&gt; 块应当覆盖共享同一种恢复策略的操作，而不是机械地追求行数少。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;嵌入式项目为什么经常禁用异常？&lt;a href=&quot;#嵌入式项目为什么经常禁用异常&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在嵌入式和硬实时系统里，避免异常往往是合理的。原因可能包括：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;栈展开延迟难以严格界定；&lt;/li&gt;
&lt;li&gt;异常处理会增加代码体积和展开表；&lt;/li&gt;
&lt;li&gt;工具链或运行库支持不完整；&lt;/li&gt;
&lt;li&gt;项目禁止动态内存分配；&lt;/li&gt;
&lt;li&gt;中断服务函数不能允许非局部控制流；&lt;/li&gt;
&lt;li&gt;安全规范要求所有控制路径显式可分析；&lt;/li&gt;
&lt;li&gt;项目直接使用了 &lt;code&gt;-fno-exceptions&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;硬实时路径&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;平均速度在硬实时控制循环里意义不大，最坏情况延迟才是约束。异常展开的最坏延迟通常难以严格界定，因此这里更适合错误码或 &lt;code&gt;std::expected&lt;/code&gt;。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;因此我不会提出”现代 C++ 就应该全面使用异常”这种宗教口号。对于嵌入式代码，下面这种组合通常很实际：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;中断、驱动、实时循环：错误码或 &lt;code&gt;expected&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;上层非实时业务逻辑：根据项目约束决定是否允许异常；&lt;/li&gt;
&lt;li&gt;初始化阶段：如果允许异常，异常往往很好用；&lt;/li&gt;
&lt;li&gt;跨 C ABI 边界：绝不让异常逃出；&lt;/li&gt;
&lt;li&gt;析构函数：通常不得抛出异常。&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;extern&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;C&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;initialize_device&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;initialize_cpp_subsystem&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;exception&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;log_error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;what&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;异常在 C++ 边界内结束，转换为 C 能理解的错误码。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;noexcept&lt;/code&gt; 是一份承诺&lt;a href=&quot;#noexcept-是一份承诺&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;可以声明某个函数不允许异常逃出：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stop_motor&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;noexcept&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;code&gt;noexcept&lt;/code&gt; 是接口承诺&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;如果异常从 &lt;code&gt;noexcept&lt;/code&gt; 函数逃出，程序会调用 &lt;code&gt;std::terminate()&lt;/code&gt;，不会继续寻找外层 &lt;code&gt;catch&lt;/code&gt;。不要为了性能随手添加 &lt;code&gt;noexcept&lt;/code&gt;；它表示函数绝不会通过异常报告失败。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;析构函数通常隐式为 &lt;code&gt;noexcept&lt;/code&gt;；但如果它的基类或成员析构函数可能抛出，这个结论也会跟着改变。如果异常从非抛出的析构函数逃出，程序会调用 &lt;code&gt;std::terminate()&lt;/code&gt;；若此时本来就在栈展开，结果同样是直接终止。&lt;/p&gt;&lt;p&gt;所以析构函数负责释放资源，但不应该通过异常报告清理失败：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Connection::&lt;/span&gt;&lt;span&gt;~&lt;/span&gt;&lt;span&gt;Connection&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;noexcept&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;close_without_throwing&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;异常安全比”有没有 catch”更重要&lt;a href=&quot;#异常安全比有没有-catch更重要&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;一个函数抛出异常后，对象和程序应该处于什么状态？&lt;/p&gt;&lt;p&gt;C++ 通常讨论三种保证：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;基本保证：没有资源泄漏，对象仍然有效，但状态可能改变。&lt;/li&gt;
&lt;li&gt;强保证：操作失败后，状态和调用前完全一致，类似事务回滚。&lt;/li&gt;
&lt;li&gt;不抛保证：函数保证不抛异常。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;例如，先构造新状态，成功后再替换旧状态：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Document&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span&gt;set_content&lt;/span&gt;&lt;span&gt;(std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new_content&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new_content&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt;       // 可能抛出，尚未修改原对象&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;content_&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;swap&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new_content&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt;  // C++17 起 std::string::swap 为 noexcept&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;验证失败时，原内容没有被修改；交换成功后，旧内容由参数 &lt;code&gt;new_content&lt;/code&gt; 在离开函数时销毁。这里给出强异常保证，而不是仅仅”接近”它。&lt;/p&gt;&lt;p&gt;相比之下：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;state_&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;clear&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;state_&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt;  // 如果这里抛出，旧状态已经丢失&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里即使外层正确捕获异常，对象也可能已经处于不理想的状态。&lt;/p&gt;&lt;p&gt;所以异常设计的真正难点不是 &lt;code&gt;try/catch&lt;/code&gt; 语法，而是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;异常安全的核心问题&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;操作在任意一步失败时，系统是否仍然保持有效状态？&lt;code&gt;catch&lt;/code&gt; 只能决定后续控制流，无法自动修复已经破坏的对象不变量。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我最终会采用的规则&lt;a href=&quot;#我最终会采用的规则&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果我要从嵌入式 C 迁移到现代 C++，我会先使用下面这套相对保守的规则：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;预期内、频繁发生、调用者立即处理的失败，使用 &lt;code&gt;std::expected&lt;/code&gt;、&lt;code&gt;std::optional&lt;/code&gt; 或错误码。&lt;/li&gt;
&lt;li&gt;罕见且无法在本地处理、需要跨越多层调用的失败，可以抛异常。&lt;/li&gt;
&lt;li&gt;构造函数无法建立有效对象时，抛异常。&lt;/li&gt;
&lt;li&gt;只在能够重试、降级、翻译、回滚或终止当前任务的边界捕获。&lt;/li&gt;
&lt;li&gt;不要每层都捕获并重新抛出。&lt;/li&gt;
&lt;li&gt;异常按值抛出，按 &lt;code&gt;const&lt;/code&gt; 引用捕获。&lt;/li&gt;
&lt;li&gt;重新抛出使用 &lt;code&gt;throw;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;用 RAII 管理资源，不要依赖 &lt;code&gt;catch&lt;/code&gt; 手动释放。&lt;/li&gt;
&lt;li&gt;析构函数、C 接口和实时路径不允许异常逃出。&lt;/li&gt;
&lt;li&gt;不用异常表示普通分支，也不用错误码掩盖真正无法继续的故障。&lt;/li&gt;
&lt;li&gt;不担心 &lt;code&gt;try&lt;/code&gt; 块里有几行代码；担心的是异常是否频繁发生，以及恢复边界是否清晰。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;最简洁的判断方式可能是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;是&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;否&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;能&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;不能&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;是&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;否&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;失败是否是正常结果?&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;使用 optional / expected / 错误码&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;当前函数能处理吗?&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;本地处理&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;是否需要跨越多层调用栈?&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;抛出异常&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;优先考虑 expected&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;是&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;否&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;能&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;不能&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;是&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;否&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;失败是否是正常结果?&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;使用 optional / expected / 错误码&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;当前函数能处理吗?&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;本地处理&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;是否需要跨越多层调用栈?&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;抛出异常&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;优先考虑 expected&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;异常最适合的角色不是代替所有错误码，而是处理那些”这里已经无法继续，但更高层仍然知道该怎么办”的失败。&lt;/p&gt;&lt;p&gt;底层负责准确报告，中层负责维持不变量，边界层负责恢复或终止。至于每个函数都写一个 &lt;code&gt;try/catch&lt;/code&gt; - 大可不必，那只是把错误码时代的 &lt;code&gt;if (error)&lt;/code&gt; 换了一件稍微昂贵一点的外套。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>我终于学会了 grep，然后换成了 ripgrep</title><link>https://bfmhno3.github.io/posts/grep-ripgrep-search-guide/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/grep-ripgrep-search-guide/</guid><description>我一直在复制 grep 命令，却没有真正理解它。这次我从单个日志文件开始，弄清模式、正则、管道和退出状态，再把同一套搜索迁移到更适合代码仓库的 ripgrep，并做了一次 126 MiB 的实测。</description><pubDate>Mon, 31 Aug 2026 07:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我每天都能看到 &lt;code&gt;grep&lt;/code&gt;：查日志时有它，读脚本时有它，甚至 AI 给出的命令里也经常有它。但我对它的理解长期停留在 “把一段神秘字符粘到管道后面”。这多少有点尴尬，所以我建了一个很小的实验目录，从一份 5 行日志开始，最后又让 &lt;code&gt;grep&lt;/code&gt; 和 &lt;code&gt;ripgrep&lt;/code&gt; 扫了 4096 个文件。搜索工具嘛，最好还是搜点东西再谈。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; 的名字来自早期 Unix 编辑器 &lt;code&gt;ed&lt;/code&gt; 中的命令 &lt;code&gt;g/re/p&lt;/code&gt;，大意是全局寻找正则表达式并打印匹配行。名字已经半个世纪了，但它今天仍然在做同一件事：&lt;strong&gt;读取文本，寻找匹配模式，输出匹配的行。&lt;/strong&gt; 它像一把文本世界里的筛子，标准输入或文件从上面倒进去，符合模式的行从下面落出来。这个比喻到这里就够了，后面我们直接看命令。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先做一份可以搜索的日志&lt;a href=&quot;#先做一份可以搜索的日志&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我创建了一个 &lt;code&gt;app.log&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;INFO boot&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;WARN cache slow&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR database timeout&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;INFO retry&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR recovered&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;最小的 &lt;code&gt;grep&lt;/code&gt; 由模式和文件组成：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;ERROR&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;database&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;recovered&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它的通用形状是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; [选项] &lt;/span&gt;&lt;span&gt;&apos;模式&apos;&lt;/span&gt;&lt;span&gt; [文件...]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我现在会默认用单引号包住模式。不是 &lt;code&gt;grep&lt;/code&gt; 强制要求，而是 shell 也认识 &lt;code&gt;*&lt;/code&gt;、&lt;code&gt;$&lt;/code&gt;、&lt;code&gt;[&lt;/code&gt; 等字符。加引号等于先告诉 shell “这段别碰，完整交给 grep”，可以少掉一类很无聊的错误。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;grep 到底匹配什么&lt;a href=&quot;#grep-到底匹配什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; 默认按&lt;strong&gt;行&lt;/strong&gt;工作。只要模式匹配一行中的任意部分，它就输出整行，而不是只输出命中的几个字符。根据 &lt;a href=&quot;https://pubs.opengroup.org/onlinepubs/9799919799/utilities/grep.html&quot; target=&quot;_blank&quot;&gt;POSIX grep 规范&lt;/a&gt;，默认模式是基本正则表达式，也就是 BRE (Basic Regular Expression)。&lt;/p&gt;&lt;p&gt;先看几个我最常用的模式：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 以 ERROR 开头&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;^ERROR&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;database&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;recovered&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 以 timeout 结尾&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;timeout$&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;database&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 匹配 WARN 或 ERROR，-E 启用扩展正则表达式&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-E&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;WARN|ERROR&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;WARN&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;slow&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;database&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;recovered&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;^&lt;/code&gt; 锚定行首，&lt;code&gt;$&lt;/code&gt; 锚定行尾，&lt;code&gt;|&lt;/code&gt; 表示二选一。这里最容易踩的坑是：默认 BRE 和 &lt;code&gt;grep -E&lt;/code&gt; 使用的 ERE (Extended Regular Expression) 语法并不完全一样。我如果需要 &lt;code&gt;|&lt;/code&gt;、&lt;code&gt;+&lt;/code&gt;、&lt;code&gt;?&lt;/code&gt; 或分组，通常直接写 &lt;code&gt;grep -E&lt;/code&gt;，而不是开始和反斜杠进行某种古老仪式。&lt;/p&gt;&lt;p&gt;如果我根本不需要正则表达式，只想搜索字面文本，就用 &lt;code&gt;-F&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-F&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;ERROR database&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ERROR&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;database&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这在搜索包含 &lt;code&gt;.&lt;/code&gt;、&lt;code&gt;[&lt;/code&gt;、&lt;code&gt;*&lt;/code&gt; 的配置或代码时尤其省心。&lt;code&gt;-F&lt;/code&gt; 的意思不是 “更弱的 grep”，而是 “不要解释特殊字符”。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我真正会记住的选项&lt;a href=&quot;#我真正会记住的选项&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;grep --help&lt;/code&gt; 很长，但日常使用不需要背完整本手册。下面这组已经覆盖了我大部分需求：&lt;/p&gt;





































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;选项&lt;/th&gt;&lt;th&gt;作用&lt;/th&gt;&lt;th&gt;示例&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-n&lt;/code&gt;&lt;/td&gt;&lt;td&gt;显示行号&lt;/td&gt;&lt;td&gt;&lt;code&gt;grep -n &apos;ERROR&apos; app.log&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-i&lt;/code&gt;&lt;/td&gt;&lt;td&gt;忽略大小写&lt;/td&gt;&lt;td&gt;&lt;code&gt;grep -i &apos;error&apos; app.log&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-v&lt;/code&gt;&lt;/td&gt;&lt;td&gt;反选不匹配的行&lt;/td&gt;&lt;td&gt;&lt;code&gt;grep -v &apos;^INFO&apos; app.log&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-c&lt;/code&gt;&lt;/td&gt;&lt;td&gt;统计匹配行数&lt;/td&gt;&lt;td&gt;&lt;code&gt;grep -c &apos;ERROR&apos; app.log&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-l&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只输出包含匹配的文件名&lt;/td&gt;&lt;td&gt;&lt;code&gt;grep -l &apos;ERROR&apos; *.log&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-w&lt;/code&gt;&lt;/td&gt;&lt;td&gt;匹配完整单词&lt;/td&gt;&lt;td&gt;&lt;code&gt;grep -w &apos;ERROR&apos; app.log&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-x&lt;/code&gt;&lt;/td&gt;&lt;td&gt;匹配完整一行&lt;/td&gt;&lt;td&gt;&lt;code&gt;grep -x &apos;INFO retry&apos; app.log&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-A 2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;输出命中行及后 2 行&lt;/td&gt;&lt;td&gt;&lt;code&gt;grep -A 2 &apos;timeout&apos; app.log&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-B 2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;输出命中行及前 2 行&lt;/td&gt;&lt;td&gt;&lt;code&gt;grep -B 2 &apos;timeout&apos; app.log&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-C 2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;输出命中行前后各 2 行&lt;/td&gt;&lt;td&gt;&lt;code&gt;grep -C 2 &apos;timeout&apos; app.log&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-E&lt;/code&gt;&lt;/td&gt;&lt;td&gt;使用扩展正则表达式&lt;/td&gt;&lt;td&gt;`grep -E ‘WARN&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-F&lt;/code&gt;&lt;/td&gt;&lt;td&gt;按字面字符串匹配&lt;/td&gt;&lt;td&gt;&lt;code&gt;grep -F &apos;a[b]&apos; file.txt&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;例如带上行号后，我的 5 行日志给出：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-E&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;WARN|ERROR&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;2:WARN&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;slow&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;3:ERROR&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;database&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;5:ERROR&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;recovered&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;统计也确实是 2 行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;ERROR&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;注意，&lt;code&gt;-c&lt;/code&gt; 统计的是&lt;strong&gt;匹配行数&lt;/strong&gt;，不是一行内出现了多少次模式。这个区别在脚本里很容易偷偷制造 bug。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;grep 为什么总出现在管道里&lt;a href=&quot;#grep-为什么总出现在管道里&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果没有文件参数，&lt;code&gt;grep&lt;/code&gt; 就从标准输入读取。于是任何能输出文本的命令都能接到它前面：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 查找进程列表中的 ssh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ps&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;aux&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;[s]sh&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 只保留日志中的错误&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;journalctl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nginx&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;error&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 在命令输出中寻找某个配置项&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;some-command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--verbose&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-F&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;cache_dir=&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;第一条里的 &lt;code&gt;[s]sh&lt;/code&gt; 是个老而实用的小技巧：它能匹配 &lt;code&gt;ssh&lt;/code&gt;，但 &lt;code&gt;grep&lt;/code&gt; 自己的命令行中出现的是 &lt;code&gt;[s]sh&lt;/code&gt;，因此不会把自己也匹配出来。不过，如果系统提供 &lt;code&gt;pgrep&lt;/code&gt;，按进程名或属性找进程通常更直接。能用结构化工具时，不必强迫文本筛子兼职数据库。&lt;/p&gt;&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; 还通过退出状态告诉脚本发生了什么：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt;：至少匹配到一行。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1&lt;/code&gt;：没有匹配。&lt;/li&gt;
&lt;li&gt;大于 &lt;code&gt;1&lt;/code&gt;：发生错误。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以检查是否存在时，我会使用 &lt;code&gt;-q&lt;/code&gt;，而不是先输出再扔掉：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-q&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;ERROR&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;found an error&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;“没有匹配” 是正常结果，不是工具坏了。理解这 3 种状态后，&lt;code&gt;grep&lt;/code&gt; 才真正从交互命令变成了可靠的脚本部件。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;从单个文件走进整个目录&lt;a href=&quot;#从单个文件走进整个目录&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;GNU grep 和很多现代实现支持递归搜索：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-R&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--include=&lt;/span&gt;&lt;span&gt;&apos;*.ts&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--exclude-dir=node_modules&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;TODO&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里 &lt;code&gt;-R&lt;/code&gt; 递归遍历目录，&lt;code&gt;--include&lt;/code&gt; 只保留 TypeScript 文件，&lt;code&gt;--exclude-dir&lt;/code&gt; 排除依赖目录。它能工作，但我必须逐项告诉 &lt;code&gt;grep&lt;/code&gt; 什么值得搜索。项目一大，命令就开始长出越来越多的过滤条件。&lt;/p&gt;&lt;p&gt;这也是我开始理解 &lt;code&gt;ripgrep&lt;/code&gt; 的位置。它并不是发明了另一种 “找字符串” 的方式，而是把&lt;strong&gt;递归搜索代码仓库&lt;/strong&gt;设成了默认场景。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; 存在 POSIX、GNU、BSD 以及其他实现，不同系统的扩展选项可能不同。&lt;code&gt;-E&lt;/code&gt;、&lt;code&gt;-F&lt;/code&gt;、&lt;code&gt;-i&lt;/code&gt;、&lt;code&gt;-n&lt;/code&gt;、&lt;code&gt;-q&lt;/code&gt;、&lt;code&gt;-v&lt;/code&gt; 等核心选项有 POSIX 定义；&lt;code&gt;-R&lt;/code&gt;、&lt;code&gt;--include&lt;/code&gt; 这样的能力应以本机 &lt;code&gt;man grep&lt;/code&gt; 或 &lt;code&gt;grep --help&lt;/code&gt; 为准。写可移植脚本时，我不会假设所有机器都运行 GNU grep。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;ripgrep：把代码仓库当成默认环境&lt;a href=&quot;#ripgrep把代码仓库当成默认环境&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;ripgrep&lt;/code&gt; 的可执行文件名是 &lt;code&gt;rg&lt;/code&gt;。它同样逐行搜索模式，但直接递归搜索当前目录：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# grep 需要显式递归和目录&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-R&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;TODO&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# rg 默认递归搜索当前目录&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;TODO&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;在 Ubuntu 或 Debian、Fedora、Arch Linux 和 macOS 上，可以分别安装：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;apt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ripgrep&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dnf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ripgrep&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pacman&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ripgrep&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ripgrep&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Windows 可以使用 Scoop：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;scoop install ripgrep&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;安装后先确认实际版本：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;rg&lt;/code&gt; 最重要的特性可能不是速度，而是它默认不搜索什么。根据 &lt;a href=&quot;https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#automatic-filtering&quot; target=&quot;_blank&quot;&gt;ripgrep 官方指南&lt;/a&gt;，它默认跳过：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.gitignore&lt;/code&gt;、&lt;code&gt;.ignore&lt;/code&gt; 和 &lt;code&gt;.rgignore&lt;/code&gt; 排除的路径。&lt;/li&gt;
&lt;li&gt;隐藏文件和隐藏目录。&lt;/li&gt;
&lt;li&gt;检测到的二进制文件。&lt;/li&gt;
&lt;li&gt;符号链接指向的内容。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;我在一个 Git 仓库里做了 3 文件的小实验：&lt;code&gt;visible.txt&lt;/code&gt;、被 &lt;code&gt;.gitignore&lt;/code&gt; 排除的 &lt;code&gt;ignored.log&lt;/code&gt;，以及隐藏文件 &lt;code&gt;.hidden.txt&lt;/code&gt;。三个文件都只有一行 &lt;code&gt;SECRET&lt;/code&gt;。为了不让 &lt;code&gt;grep&lt;/code&gt; 顺手钻进 &lt;code&gt;.git&lt;/code&gt;，我显式排除了它：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-R&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--exclude-dir=.git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;SECRET&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;./.hidden.txt:1:SECRET&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hidden&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;./visible.txt:1:SECRET&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;visible&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;./ignored.log:1:SECRET&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ignored&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;SECRET&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;./visible.txt:1:SECRET&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;visible&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;同一个目录里，&lt;code&gt;grep&lt;/code&gt; 找到 3 行，&lt;code&gt;rg&lt;/code&gt; 默认只给我 1 行。&lt;code&gt;rg&lt;/code&gt; 不是漏搜了，它执行了更适合代码仓库的过滤策略。&lt;/p&gt;&lt;p&gt;需要掀开过滤器时，也有明确的开关：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 搜索被 ignore 规则排除的路径&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--no-ignore&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;SECRET&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 同时搜索隐藏文件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--hidden&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--no-ignore&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;SECRET&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# -u 逐级解除限制：-u 忽略 ignore，-uu 包含隐藏文件，-uuu 还包含二进制文件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-uuu&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;SECRET&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-uuu&lt;/code&gt; 很好记，但我不会把它设成默认别名。二进制内容可能把控制字符直接送进终端，而且 &lt;code&gt;node_modules&lt;/code&gt;、构建产物和 &lt;code&gt;.git&lt;/code&gt; 往往正是噪声的主要来源。只有当 “我明明知道它在这里” 时，我才逐级加 &lt;code&gt;u&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;rg 中最顺手的几种搜索&lt;a href=&quot;#rg-中最顺手的几种搜索&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;多数 &lt;code&gt;grep&lt;/code&gt; 习惯可以直接迁移：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;error&apos;&lt;/span&gt;&lt;span&gt;                 &lt;/span&gt;&lt;span&gt;# 忽略大小写&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-F&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;array[index]&apos;&lt;/span&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;# 字面字符串&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-w&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;Result&apos;&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;# 完整单词&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-C&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;panic&apos;&lt;/span&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;# 前后各 3 行上下文&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;TODO|FIXME&apos;&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;# 只列出文件名&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;TODO&apos;&lt;/span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;# 按文件统计匹配行数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-q&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;needle&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;path/to/file&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# 静默检查，依靠退出状态&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;针对仓库筛选文件时，glob 比一长串目录参数更舒服：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;fetchData&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-g&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;*.ts&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-g&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;*.tsx&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;password&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-g&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;!dist/**&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-g&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;!*.min.js&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-g&lt;/code&gt; 可以重复使用，前面的 &lt;code&gt;!&lt;/code&gt; 表示排除。记得给 glob 加单引号，否则 shell 可能在 &lt;code&gt;rg&lt;/code&gt; 看到它之前就先展开。&lt;/p&gt;&lt;p&gt;&lt;code&gt;rg&lt;/code&gt; 还内置文件类型：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-tpy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;def main&apos;&lt;/span&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt;# 只搜索 Python&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-tts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;fetchData&apos;&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# 只搜索 TypeScript&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-Tjs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;TODO&apos;&lt;/span&gt;&lt;span&gt;           &lt;/span&gt;&lt;span&gt;# 排除 JavaScript&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--type-list&lt;/span&gt;&lt;span&gt;           &lt;/span&gt;&lt;span&gt;# 查看当前支持的类型&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里的文件类型本质上是一组扩展名和 glob，不是在做语法分析。如果我需要寻找真正的函数调用、声明或引用，AST 或语言服务器才是对的工具。正则表达式看起来像一把锤子，而代码仓库里碰巧到处都是长得像钉子的字符串，仅此而已。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;两套正则表达式并不完全相同&lt;a href=&quot;#两套正则表达式并不完全相同&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; 默认使用 BRE，&lt;code&gt;grep -E&lt;/code&gt; 使用 ERE。&lt;code&gt;rg&lt;/code&gt; 默认使用 Rust 的 regex 引擎，支持 Unicode，并保证线性时间搜索，但默认不支持反向引用和 look-around。简单的搜索通常可以直接对应：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-E&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;WARN|ERROR&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;WARN|ERROR&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如果确实需要 look-around 或反向引用，并且当前 &lt;code&gt;rg&lt;/code&gt; 构建包含 PCRE2，可以使用 &lt;code&gt;-P&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-P&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;ERROR(?= database)&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我不会因为 PCRE2 更 “强” 就默认打开它。大多数代码搜索只需要字面量、字符类、分支和重复；默认引擎更可预测，也更不容易写出性能悬崖。复杂正则如果已经需要一杯咖啡才能读懂，通常也到了该换脚本或结构化工具的时候。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我测了一次速度，但不准备宣布宇宙真理&lt;a href=&quot;#我测了一次速度但不准备宣布宇宙真理&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;为了不只复述 “ripgrep 很快”，我生成了 4096 个文本文件，并让其中每 16 个文件有 1 个包含固定字符串 &lt;code&gt;needle-search-marker&lt;/code&gt;。当时我在 Python 会话中直接执行实验；下面把同一段生成逻辑整理成可独立运行的 &lt;code&gt;generate_benchmark.py&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#!/usr/bin/env python3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; pathlib &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Path&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;ROOT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Path&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;bench&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;FILE_COUNT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;4096&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;MATCH_INTERVAL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;PATTERN&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;&quot;needle-search-marker&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ROOT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exists&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SystemExit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;bench/ already exists; remove it before regenerating&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;ROOT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;line &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;alpha beta gamma delta epsilon 0123456789 &quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;plain_payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; line &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;48&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;FILE_COUNT&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; plain_payload&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MATCH_INTERVAL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;payload &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PATTERN&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ROOT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;file-&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;:04d&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;.txt&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;write_bytes&lt;/span&gt;&lt;span&gt;(payload)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;total_bytes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt;&lt;span&gt;(path.&lt;/span&gt;&lt;span&gt;stat&lt;/span&gt;&lt;span&gt;().st_size &lt;/span&gt;&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; path &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ROOT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;iterdir&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;matching_files &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;FILE_COUNT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MATCH_INTERVAL&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;files: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;FILE_COUNT&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;size: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;total_bytes&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; bytes (&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&lt;span&gt;total_bytes &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1024&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1024&lt;/span&gt;&lt;span&gt;:.2f&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; MiB)&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;matching files: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;matching_files&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这组参数最终生成 4096 个文件，总大小为 132322560 bytes，也就是 126.19 MiB，其中 256 个文件包含目标字符串。接下来的两组命令都会搜索这份固定语料。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;generate_benchmark.py&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;files:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4096&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;size:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;132322560&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt; (126.19 &lt;/span&gt;&lt;span&gt;MiB&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;matching&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;files:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;256&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;下面再把同一次实验的计时逻辑整理成 &lt;code&gt;benchmark.py&lt;/code&gt;。它先让每个工具预热 1 次，使语料进入系统缓存，然后正式运行 9 次。&lt;code&gt;time.perf_counter()&lt;/code&gt; 测量的是从创建子进程到进程退出的墙钟时间，命令输出则送到 &lt;code&gt;/dev/null&lt;/code&gt;，避免终端渲染成为主要开销。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#!/usr/bin/env python3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; pathlib &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Path&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; statistics&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; subprocess&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;ROOT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Path&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;bench&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;PATTERN&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;needle-search-marker&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;RUNS&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;9&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ROOT&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;is_dir&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SystemExit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;bench/ does not exist; run generate_benchmark.py first&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;commands &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;grep&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;grep&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-R&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-l&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-F&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;PATTERN&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ROOT&lt;/span&gt;&lt;span&gt;)],&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;rg&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;rg&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-l&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-F&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;PATTERN&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ROOT&lt;/span&gt;&lt;span&gt;)],&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;timings &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; command &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; commands.&lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;subprocess.&lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;(command, &lt;/span&gt;&lt;span&gt;stdout&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;subprocess.&lt;/span&gt;&lt;span&gt;DEVNULL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; name, command &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; commands.&lt;/span&gt;&lt;span&gt;items&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;samples &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; _ &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;RUNS&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;started &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; time.&lt;/span&gt;&lt;span&gt;perf_counter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;subprocess.&lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;(command, &lt;/span&gt;&lt;span&gt;stdout&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;subprocess.&lt;/span&gt;&lt;span&gt;DEVNULL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;samples.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;((time.&lt;/span&gt;&lt;span&gt;perf_counter&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; started) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;timings[name] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; samples&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; name, samples &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; timings.&lt;/span&gt;&lt;span&gt;items&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;formatted &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;, &quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;sample&lt;/span&gt;&lt;span&gt;:.2f&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; sample &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; samples)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;formatted&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; ms&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; median: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&lt;span&gt;statistics.&lt;/span&gt;&lt;span&gt;median&lt;/span&gt;&lt;span&gt;(samples)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:.2f&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; ms&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;ratio &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; statistics.&lt;/span&gt;&lt;span&gt;median&lt;/span&gt;&lt;span&gt;(timings[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;grep&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; statistics.&lt;/span&gt;&lt;span&gt;median&lt;/span&gt;&lt;span&gt;(timings[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;rg&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;grep / rg: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ratio&lt;/span&gt;&lt;span&gt;:.2f&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;x&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这份计时脚本通过 &lt;code&gt;subprocess.run()&lt;/code&gt; 直接启动外部可执行文件，不经过 shell；在我的环境中，它解析到 GNU grep 3.12 和 ripgrep 15.1.0。因此，下面的数据确实来自 GNU grep 3.12，而不是其他 &lt;code&gt;grep&lt;/code&gt; 实现。9 次原始数据按脚本记录的执行顺序列出，没有挑选或删除离群值：&lt;/p&gt;



























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;次数&lt;/th&gt;&lt;th&gt;&lt;code&gt;grep&lt;/code&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;rg&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;44.67 ms&lt;/td&gt;&lt;td&gt;13.90 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;45. 36ms&lt;/td&gt;&lt;td&gt;14.66 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;43.01 ms&lt;/td&gt;&lt;td&gt;14.96 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;42.65 ms&lt;/td&gt;&lt;td&gt;15.72 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;44.68 ms&lt;/td&gt;&lt;td&gt;15.91 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;42.72 ms&lt;/td&gt;&lt;td&gt;11.92 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;44.74 ms&lt;/td&gt;&lt;td&gt;15.04 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;43.28 ms&lt;/td&gt;&lt;td&gt;13.66 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;44.01 ms&lt;/td&gt;&lt;td&gt;14.59 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;中位数&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;44.01 ms&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;14.66 ms&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;两个命令实际分别是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-R&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-F&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;needle-search-marker&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bench/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;rg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-F&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;needle-search-marker&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bench/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;因此，中位时间和相对值为：&lt;/p&gt;



















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;工具&lt;/th&gt;&lt;th&gt;中位时间&lt;/th&gt;&lt;th&gt;相对值&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;grep&lt;/code&gt;&lt;/td&gt;&lt;td&gt;44.01 ms&lt;/td&gt;&lt;td&gt;3.00x&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;rg&lt;/code&gt;&lt;/td&gt;&lt;td&gt;14.66 ms&lt;/td&gt;&lt;td&gt;1.00x&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这次 &lt;code&gt;rg&lt;/code&gt; 大约快 3 倍。数字很具体，结论却必须克制：它只描述这台机器、这两个版本、热缓存、固定字符串和 4096 个文件。换成单个大文件、高匹配率、不同正则、机械硬盘或另一个 &lt;code&gt;grep&lt;/code&gt; 实现，比例都可能变化。&lt;code&gt;ripgrep&lt;/code&gt; 项目的 &lt;a href=&quot;https://github.com/BurntSushi/ripgrep#quick-examples-comparing-tools&quot; target=&quot;_blank&quot;&gt;官方 README&lt;/a&gt; 也明确提醒，单个 benchmark 永远不够。&lt;/p&gt;&lt;p&gt;我反而更在意前一个实验：在包含忽略文件和隐藏文件的目录中，默认输出从 3 行降到了 1 行。少掉的 2 行不是性能指标，而是我原本就不想看的噪声。这种默认行为每天都会影响搜索体验，44 ms 和 15 ms 则未必。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我现在怎么选&lt;a href=&quot;#我现在怎么选&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我的规则最后变得很简单：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;脚本要跨陌生的 Unix 环境运行&lt;/strong&gt;：优先使用 POSIX &lt;code&gt;grep&lt;/code&gt; 的核心选项。它普遍存在，而且契约有标准。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;搜索单个文件或管道输出&lt;/strong&gt;：&lt;code&gt;grep&lt;/code&gt; 已经足够直接，尤其是系统上不一定安装 &lt;code&gt;rg&lt;/code&gt; 时。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交互式搜索代码仓库&lt;/strong&gt;：优先使用 &lt;code&gt;rg&lt;/code&gt;。递归、ignore、隐藏文件过滤、glob 和文件类型都围绕这个场景设计。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;搜索结果疑似被过滤&lt;/strong&gt;：先试 &lt;code&gt;rg -u&lt;/code&gt;、&lt;code&gt;rg --hidden&lt;/code&gt; 或 &lt;code&gt;rg --debug&lt;/code&gt;，不要立刻断言文件不存在。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要理解代码结构&lt;/strong&gt;：停止继续堆正则，换语言服务器、AST 搜索或 IDE 引用查找。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; 没有因为 &lt;code&gt;ripgrep&lt;/code&gt; 出现就过时。它更像 shell 世界里的基础协议，而 &lt;code&gt;rg&lt;/code&gt; 是针对现代代码仓库重新安排过默认值的高性能客户端。我大概会继续在脚本里写 &lt;code&gt;grep&lt;/code&gt;，在终端里敲 &lt;code&gt;rg&lt;/code&gt;。至少下一次看到 &lt;code&gt;grep -Eiv&lt;/code&gt; 时，我不再需要假装那是一串不可分割的魔法咒语了。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;继续查阅&lt;a href=&quot;#继续查阅&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pubs.opengroup.org/onlinepubs/9799919799/utilities/grep.html&quot; target=&quot;_blank&quot;&gt;POSIX grep 规范&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gnu.org/software/grep/manual/grep.html&quot; target=&quot;_blank&quot;&gt;GNU grep 3.12 手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md&quot; target=&quot;_blank&quot;&gt;ripgrep User Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/BurntSushi/ripgrep/blob/master/README.md&quot; target=&quot;_blank&quot;&gt;ripgrep README 与性能说明&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded></item><item><title>从零学习 vcpkg: 用现代 CMake 做一个 HTTP CLI</title><link>https://bfmhno3.github.io/posts/vcpkg-modern-cmake-http-client/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/vcpkg-modern-cmake-http-client/</guid><description>我用 vcpkg、现代 CMake、CPR、CLI11 和 spdlog 做了一个可运行的命令行 HTTP 客户端，并沿着清单、baseline、triplet、toolchain 和 imported target 把 C/C++ 依赖管理串起来。</description><pubDate>Sun, 30 Aug 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我过去写的嵌入式项目主要使用 C，依赖通常是芯片厂商 SDK、几份直接放进仓库的源码，以及一些自己维护的驱动，所以我很少认真面对 C/C++ 包管理。Python 有 pip，JavaScript/TypeScript 有 npm 和 pnpm，Rust 有 Cargo；C++ 则把编译器、构建系统和包管理器拆成了三件事。于是我决定不再只背 &lt;code&gt;vcpkg install&lt;/code&gt;，而是用 vcpkg、现代 CMake 和 3 个第三方库做一个真的能发出请求的 HTTP CLI，然后看看每一层到底负责什么。&lt;/p&gt;
&lt;p&gt;这个小程序叫 &lt;code&gt;vhttp&lt;/code&gt;。它支持 GET、POST、自定义 Header、超时和详细日志。CLI11 解析命令行，CPR 发送 HTTP 请求，spdlog 记录状态。3 个直接依赖最后展开成 10 个需要安装的 port，刚好足以看到包管理器开始工作，又没有膨胀成另一个 curl（我只是来学 vcpkg，不是来重写互联网的）。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先把 3 个角色分开&lt;a href=&quot;#先把-3-个角色分开&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;C/C++ 依赖管理容易混乱，通常不是因为命令太多，而是因为几种工具同时出现在终端里：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;编译器&lt;/strong&gt;：GCC、Clang 或 MSVC，把源文件变成目标文件，并参与链接。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CMake&lt;/strong&gt;：构建系统生成器，描述 target、源文件、编译选项和链接关系，再生成 Ninja、Makefile 或 Visual Studio 工程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;vcpkg&lt;/strong&gt;：包管理器，解析依赖图，取得源码和 port 配方，按目标平台构建并安装库，再把它们交给 CMake 查找。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;我把 vcpkg 想成仓库管理员，把 CMake 想成装配清单。仓库管理员负责把零件放到正确货架，装配清单负责说明 &lt;code&gt;vhttp&lt;/code&gt; 需要哪些零件以及如何连接。二者会合作，但不是同一个东西。&lt;/p&gt;&lt;p&gt;vcpkg 也不等于一个只下载预编译二进制的商店。它的 registry 主要保存 port 的版本信息、补丁和构建配方，很多库会在本机按 triplet 编译。第一次安装 OpenSSL 或 Boost 时看到编译器忙起来，不是 vcpkg 突然兼职写代码，而是它正在按配方生产当前平台需要的产物。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;安装 vcpkg&lt;a href=&quot;#安装-vcpkg&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;官方的通用安装方式是克隆仓库并运行 bootstrap 脚本：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://github.com/microsoft/vcpkg.git&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;./bootstrap-vcpkg.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-disableMetrics&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Windows PowerShell 对应：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;git clone https:&lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;/span&gt;&lt;span&gt;github.com&lt;/span&gt;&lt;span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;microsoft&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;vcpkg.git&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd vcpkg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.\&lt;/span&gt;&lt;span&gt;bootstrap-vcpkg.bat&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;disableMetrics&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;bootstrap 会准备 &lt;code&gt;vcpkg&lt;/code&gt; 可执行文件。然后设置 &lt;code&gt;VCPKG_ROOT&lt;/code&gt;，并把可执行文件加入 &lt;code&gt;PATH&lt;/code&gt;。Linux 或 macOS 可以写入自己的 shell 配置：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;VCPKG_ROOT&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/tools/vcpkg&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PATH&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$VCPKG_ROOT&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;$PATH&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;PowerShell 当前会话可以这样设置：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$env:VCPKG_ROOT&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;C:\tools\vcpkg&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$env:PATH&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;/span&gt;&lt;span&gt;env:VCPKG_ROOT&lt;/span&gt;&lt;span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;/span&gt;&lt;span&gt;env:PATH&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;最后检查实际入口，而不是假设 bootstrap 已经替我们处理了所有环境变量：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$ vcpkg version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg package management program version 2026-07-27-98d7cb0cf1f4686a3e43aa5672b6230c1d56bce8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;版本会变化。这里真正重要的是命令可以运行，并且 &lt;code&gt;VCPKG_ROOT&lt;/code&gt; 指向包含 &lt;code&gt;scripts/buildsystems/vcpkg.cmake&lt;/code&gt; 的那份 vcpkg。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Classic mode 和 Manifest mode&lt;a href=&quot;#classic-mode-和-manifest-mode&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;vcpkg 有两种工作模式：&lt;/p&gt;






















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;模式&lt;/th&gt;&lt;th&gt;依赖声明&lt;/th&gt;&lt;th&gt;安装位置&lt;/th&gt;&lt;th&gt;适合场景&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Classic mode&lt;/td&gt;&lt;td&gt;主要靠命令安装&lt;/td&gt;&lt;td&gt;&lt;code&gt;$VCPKG_ROOT/installed&lt;/code&gt;&lt;/td&gt;&lt;td&gt;临时试用、旧项目&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Manifest mode&lt;/td&gt;&lt;td&gt;项目提交 &lt;code&gt;vcpkg.json&lt;/code&gt;&lt;/td&gt;&lt;td&gt;每个项目自己的安装树&lt;/td&gt;&lt;td&gt;正式项目、版本控制、CI&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Classic mode 的体验接近系统级仓库：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fmt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fmt:x64-windows&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这很适合快速试一个库，但依赖关系存在于开发者的机器里。换一台机器后，项目本身没有完整声明告诉我们该装什么。&lt;/p&gt;&lt;p&gt;Manifest mode 把直接依赖写进 &lt;code&gt;vcpkg.json&lt;/code&gt;，并为每个项目维护独立安装树。它也支持 baseline、版本约束、override 和自定义 registry。对新项目，我会直接使用 Manifest mode。把依赖写进仓库，比把它们记在脑内某个没有备份的 YAML 文件里可靠得多。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;建立 &lt;code&gt;vhttp&lt;/code&gt;&lt;a href=&quot;#建立-vhttp&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;先创建目录并初始化应用清单：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;vhttp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;vhttp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--application&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;port&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cli11&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;port&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cpr&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;port&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;spdlog&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;vcpkg new --application&lt;/code&gt; 会创建项目清单和 registry 配置，&lt;code&gt;vcpkg add port&lt;/code&gt; 则把直接依赖写进清单。项目最终结构如下：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vhttp/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── CMakeLists.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── CMakePresets.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── src/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│   └── main.cpp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├── vcpkg-configuration.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└── vcpkg.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我还会忽略本机构建产物：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/build/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/vcpkg_installed/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CMakeUserPresets.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;vcpkg.json&lt;/code&gt; 很短：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;vhttp&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;version-semver&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0.1.0&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;cli11&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;cpr&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;spdlog&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它只声明&lt;strong&gt;直接依赖&lt;/strong&gt;。CPR 需要 curl，curl 的 HTTPS 支持需要 OpenSSL 和 zlib，spdlog 默认又需要 fmt；这些传递依赖由 vcpkg 解图，不应该为了复制一次安装输出就全部手写进 &lt;code&gt;dependencies&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;code&gt;vcpkg-configuration.json&lt;/code&gt; 保存 registry 和 baseline：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;default-registry&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;kind&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;git&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;baseline&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;1390a3d90e08a7901a7781d31332a662102cd5cc&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;repository&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://github.com/microsoft/vcpkg&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;baseline 是 vcpkg registry 的一个 Git commit。它给整张依赖图一个确定的版本地板，让两台机器不要因为各自恰好在不同日期安装，就悄悄选到不同 port 版本。上面的 commit 是我这次实验实际使用的快照；新项目应该保留 &lt;code&gt;vcpkg new&lt;/code&gt; 为自己生成的值，而不是机械复制它。&lt;/p&gt;&lt;p&gt;需要主动更新依赖视野时，可以运行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x-update-baseline&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;更新 baseline 是一个明确的依赖升级动作，应该连同构建结果一起审查和提交。它不是每次构建前都要念一遍的咒语。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;baseline 并不等于传统意义上的 lockfile。它确定 registry 快照；&lt;code&gt;version&amp;gt;=&lt;/code&gt; 表达最低版本，&lt;code&gt;overrides&lt;/code&gt; 可以强制精确版本。大多数应用先固定 baseline，只在确有需要时增加版本约束或 override，会比给每个传递依赖手工钉死版本更容易维护。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;用 CMake Presets 接上 vcpkg&lt;a href=&quot;#用-cmake-presets-接上-vcpkg&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;vcpkg 通过一个 CMake toolchain 文件接入配置过程。关键点是：CMake 在执行第一次 &lt;code&gt;project()&lt;/code&gt; 时就会确定工具链，所以 &lt;code&gt;CMAKE_TOOLCHAIN_FILE&lt;/code&gt; 必须在那之前可见。&lt;/p&gt;&lt;p&gt;我不把本机的绝对路径写进 &lt;code&gt;CMakeLists.txt&lt;/code&gt;，而是提交一份 &lt;code&gt;CMakePresets.json&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;version&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;configurePresets&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;dev&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;displayName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Development build with vcpkg&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;generator&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Ninja&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;binaryDir&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;${sourceDir}/build/${presetName}&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;cacheVariables&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;CMAKE_BUILD_TYPE&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Debug&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;CMAKE_EXPORT_COMPILE_COMMANDS&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;CMAKE_TOOLCHAIN_FILE&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;buildPresets&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;dev&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;configurePreset&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;dev&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Preset 固定了生成器、构建目录和共享配置，但通过环境变量取得每台机器自己的 vcpkg 路径。团队可以提交这份文件；如果某人不想全局设置 &lt;code&gt;VCPKG_ROOT&lt;/code&gt;，也可以在不提交的 &lt;code&gt;CMakeUserPresets.json&lt;/code&gt; 中继承 &lt;code&gt;dev&lt;/code&gt; 并设置本机环境。&lt;/p&gt;&lt;p&gt;配置时，vcpkg toolchain 会发现项目根目录中的 manifest，并自动执行安装：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cmake&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--preset&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我第一次配置时声明了 3 个直接依赖，vcpkg 实际列出了 10 个 port：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cli11&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cpr&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;curl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;fmt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;openssl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;spdlog&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg-cmake&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg-cmake-config&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg-cmake-get-vars&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;zlib&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;从 3 个名字展开到 10 个构建节点，这就是手工下载压缩包、复制 include 路径和寻找 &lt;code&gt;.a&lt;/code&gt;/&lt;code&gt;.lib&lt;/code&gt; 文件开始变得不太可爱的地方。Manifest mode 会把这棵安装树放进当前构建目录的 &lt;code&gt;vcpkg_installed&lt;/code&gt;，不会污染另一个项目。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;现代 CMake 只围绕 target 工作&lt;a href=&quot;#现代-cmake-只围绕-target-工作&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;根目录的 &lt;code&gt;CMakeLists.txt&lt;/code&gt; 如下：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cmake_minimum_required&lt;/span&gt;&lt;span&gt;(VERSION 3.25)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;project&lt;/span&gt;&lt;span&gt;(vhttp VERSION 0.1.0 LANGUAGES CXX)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;find_package&lt;/span&gt;&lt;span&gt;(CLI11 CONFIG REQUIRED)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;find_package&lt;/span&gt;&lt;span&gt;(cpr CONFIG REQUIRED)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;find_package&lt;/span&gt;&lt;span&gt;(spdlog CONFIG REQUIRED)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;add_executable&lt;/span&gt;&lt;span&gt;(vhttp)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_sources&lt;/span&gt;&lt;span&gt;(vhttp PRIVATE src/main.cpp)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_compile_features&lt;/span&gt;&lt;span&gt;(vhttp PRIVATE cxx_std_20)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_link_libraries&lt;/span&gt;&lt;span&gt;(vhttp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PRIVATE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI11::CLI11&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cpr::cpr&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;spdlog::spdlog&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_compile_options&lt;/span&gt;&lt;span&gt;(vhttp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PRIVATE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;$&amp;lt;CXX_COMPILER_ID:&lt;/span&gt;&lt;span&gt;MSVC&lt;/span&gt;&lt;span&gt;&amp;gt;:/W4&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;$&amp;lt;CXX_COMPILER_ID:&lt;/span&gt;&lt;span&gt;MSVC&lt;/span&gt;&lt;span&gt;&amp;gt;:/permissive-&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;$&amp;lt;&lt;/span&gt;&lt;span&gt;NOT&lt;/span&gt;&lt;span&gt;:$&amp;lt;CXX_COMPILER_ID:&lt;/span&gt;&lt;span&gt;MSVC&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;:-Wall&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;$&amp;lt;&lt;/span&gt;&lt;span&gt;NOT&lt;/span&gt;&lt;span&gt;:$&amp;lt;CXX_COMPILER_ID:&lt;/span&gt;&lt;span&gt;MSVC&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;:-Wextra&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;$&amp;lt;&lt;/span&gt;&lt;span&gt;NOT&lt;/span&gt;&lt;span&gt;:$&amp;lt;CXX_COMPILER_ID:&lt;/span&gt;&lt;span&gt;MSVC&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;:-Wpedantic&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这里没有全局 &lt;code&gt;include_directories()&lt;/code&gt;，没有硬编码 &lt;code&gt;lib&lt;/code&gt; 路径，也没有 &lt;code&gt;link_directories()&lt;/code&gt;。&lt;code&gt;find_package(... CONFIG REQUIRED)&lt;/code&gt; 找到库导出的 CMake package config，&lt;code&gt;CLI11::CLI11&lt;/code&gt;、&lt;code&gt;cpr::cpr&lt;/code&gt; 和 &lt;code&gt;spdlog::spdlog&lt;/code&gt; 则是 imported target。&lt;/p&gt;&lt;p&gt;这些 target 自带 include 目录、编译定义和传递链接要求。&lt;code&gt;vhttp&lt;/code&gt; 只需要声明”我以 &lt;code&gt;PRIVATE&lt;/code&gt; 方式链接它们”。这就是现代 CMake 最重要的习惯：让 target 携带 usage requirements，而不是把一堆全局状态泼到整个目录上。&lt;/p&gt;&lt;p&gt;&lt;code&gt;target_compile_features(vhttp PRIVATE cxx_std_20)&lt;/code&gt; 也比手工拼 &lt;code&gt;-std=c++20&lt;/code&gt; 更可移植。警告选项确实因编译器而不同，所以我用 generator expression 在生成阶段选择 MSVC 或 GCC/Clang 的参数。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;写一个真的 HTTP 客户端&lt;a href=&quot;#写一个真的-http-客户端&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;src/main.cpp&lt;/code&gt; 的完整实现如下：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;algorithm&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;cctype&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;stdexcept&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;string&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;vector&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;CLI/CLI.hpp&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;cpr/cpr.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;spdlog/spdlog.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;namespace&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cpr::&lt;/span&gt;&lt;span&gt;Header&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ParseHeaders&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;raw_headers&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cpr::&lt;/span&gt;&lt;span&gt;Header&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;raw_headers&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;colon&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;:&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;colon&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; std::string::&lt;/span&gt;&lt;span&gt;npos&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;colon&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;runtime_error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;invalid header, expected &apos;Name: Value&apos;: &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                               &lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value_start&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;colon&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value_start&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;value_start&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos; &apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;value_start&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;emplace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;substr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;colon&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;substr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value_start&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ToUpper&lt;/span&gt;&lt;span&gt;(std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;ranges&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;begin&lt;/span&gt;&lt;span&gt;(), [](&lt;/span&gt;&lt;span&gt;unsigned&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;static_cast&lt;/span&gt;&lt;span&gt;&amp;lt;char&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span&gt;toupper&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  // namespace&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;argc&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;char*&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;argv&lt;/span&gt;&lt;span&gt;[]) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI::&lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;A tiny HTTP client built with vcpkg&quot;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;GET&quot;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;raw_headers&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeout_ms&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;5000&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;verbose&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;50&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;51&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_option&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;url&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Request URL&quot;&lt;/span&gt;&lt;span&gt;)-&amp;gt;&lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;52&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_option&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;-X,--request&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;HTTP method: GET or POST&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;53&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_option&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;-H,--header&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;raw_headers&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Request header, repeatable&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;54&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_option&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;-d,--data&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;POST request body&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;55&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_option&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;--timeout&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;timeout_ms&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Timeout in milliseconds&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;56&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;span&gt;(CLI::&lt;/span&gt;&lt;span&gt;PositiveNumber&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;57&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_flag&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;-v,--verbose&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;verbose&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Enable debug logs&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;58&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;59&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;CLI11_PARSE&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;argc&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;argv&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;60&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;61&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ToUpper&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;62&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;GET&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;POST&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;63&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;spdlog::&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;unsupported method: {}&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;64&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;65&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;66&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;67&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;68&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;spdlog::&lt;/span&gt;&lt;span&gt;set_level&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;verbose&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; spdlog::level::&lt;/span&gt;&lt;span&gt;debug&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; spdlog::level::&lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;69&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;spdlog::&lt;/span&gt;&lt;span&gt;debug&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;{} {} (timeout={} ms)&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;timeout_ms&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;70&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;71&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cpr::&lt;/span&gt;&lt;span&gt;Session&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;72&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetUrl&lt;/span&gt;&lt;span&gt;(cpr::&lt;/span&gt;&lt;span&gt;Url&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;73&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetHeader&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ParseHeaders&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;raw_headers&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;74&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetTimeout&lt;/span&gt;&lt;span&gt;(cpr::&lt;/span&gt;&lt;span&gt;Timeout&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;timeout_ms&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;75&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;76&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cpr::&lt;/span&gt;&lt;span&gt;Response&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;77&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;POST&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;78&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetBody&lt;/span&gt;&lt;span&gt;(cpr::&lt;/span&gt;&lt;span&gt;Body&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;79&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Post&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;80&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;81&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Get&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;82&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;83&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;84&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; cpr::ErrorCode::&lt;/span&gt;&lt;span&gt;OK&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;85&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;spdlog::&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;request failed: {}&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;86&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;87&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;88&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;89&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;spdlog::&lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;HTTP {} in {:.0f} ms, {} bytes&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;status_code&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;90&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                 &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;elapsed&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1000.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;91&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;92&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ends_with&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;93&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;94&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;95&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;96&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;status_code&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;97&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;exception&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;98&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;spdlog::&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;{}&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;what&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;99&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;100&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;101&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这里有一个刻意的接口设计：spdlog 把诊断信息写到标准错误，响应 body 写到标准输出。因此可以把 body 继续交给其他程序，而日志不会混进数据流：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vhttp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://example.com/api&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;jq&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;jq&lt;/code&gt; 是一个处理 JSON 的命令行工具；管道符 &lt;code&gt;|&lt;/code&gt; 会把 &lt;code&gt;vhttp&lt;/code&gt; 写到标准输出的响应 body 交给 &lt;code&gt;jq&lt;/code&gt;，最后的 &lt;code&gt;.&lt;/code&gt; 表示读取并格式化整个 JSON 值。例如服务返回一行紧凑 JSON 时，&lt;code&gt;jq .&lt;/code&gt; 会把它缩进为更容易阅读的多行结构。&lt;code&gt;jq&lt;/code&gt; 不是 &lt;code&gt;vhttp&lt;/code&gt; 的依赖，需要按操作系统单独安装；如果不需要格式化 JSON，直接运行 &lt;code&gt;vhttp https://example.com/api&lt;/code&gt; 即可。&lt;/p&gt;&lt;p&gt;返回码也区分了几类失败：参数或 Header 错误返回 1，传输错误返回 2，HTTP 4xx/5xx 返回 3。HTTP 404 和 TCP 连接失败不是一回事，CLI 最好不要把它们都压成一个含糊的 &lt;code&gt;false&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;配置、构建和运行&lt;a href=&quot;#配置构建和运行&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;有了 configure preset 和 build preset，基本循环只有两条命令：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cmake&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--preset&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cmake&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--build&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--preset&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我的实际构建只有 2 个 Ninja step：编译 &lt;code&gt;main.cpp&lt;/code&gt;，然后链接 &lt;code&gt;vhttp&lt;/code&gt;。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;[1/2] Building CXX object CMakeFiles/vhttp.dir/src/main.cpp.o&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;[2/2] Linking CXX executable vhttp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;为了避免把外部网络状态混进验证，我使用 Python 3 标准库自带的 &lt;code&gt;http.server&lt;/code&gt; 在本机启动了一个静态文件服务器。它不需要额外安装 Web 框架，只要系统中的 &lt;code&gt;python3&lt;/code&gt; 命令可用即可。&lt;/p&gt;&lt;p&gt;我先在项目根目录创建测试目录和响应文件。&lt;code&gt;printf&lt;/code&gt; 不会额外改写内容，因此这个文件恰好是 17 bytes：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;http-fixture&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;hello from vhttp\n&apos;&lt;/span&gt;&lt;span&gt; &amp;gt; &lt;/span&gt;&lt;span&gt;http-fixture/hello.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;然后打开第一个终端，在项目根目录启动服务器：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;python3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;http.server&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;18080&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;--bind&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;127.0.0.1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;--directory&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;http-fixture&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Serving&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HTTP&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;127.0.0.1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;port&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;18080&lt;/span&gt;&lt;span&gt; (http://127.0.0.1:18080/) ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-m http.server&lt;/code&gt; 让 Python 运行标准库中的静态 HTTP server 模块。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;18080&lt;/code&gt; 是监听端口。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--bind 127.0.0.1&lt;/code&gt; 只允许本机访问，避免把临时测试服务暴露到局域网。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--directory http-fixture&lt;/code&gt; 把刚才创建的目录作为网站根目录。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这条命令会占用当前终端并持续运行。验证结束后按 &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;C&lt;/kbd&gt; 停止服务。Windows 如果安装了 Python Launcher，也可以把命令开头的 &lt;code&gt;python3&lt;/code&gt; 换成 &lt;code&gt;py&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;接着打开第二个终端，仍然进入 &lt;code&gt;vhttp&lt;/code&gt; 项目根目录，再运行客户端。URL 中的 &lt;code&gt;/hello.txt&lt;/code&gt; 对应 &lt;code&gt;http-fixture/hello.txt&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./build/dev/vhttp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;http://127.0.0.1:18080/hello.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;[debug] GET http://127.0.0.1:18080/hello.txt (&lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;5000&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ms&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;[info] HTTP 200 in 76 ms, 17 bytes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;hello&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;vhttp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;POST 和重复 Header 使用相同的可执行文件：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;./build/dev/vhttp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-X&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-H&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-H&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Accept: application/json&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;{&quot;message&quot;:&quot;hello&quot;}&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;https://httpbin.org/post&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这个外部 URL 只用于演示命令形式，服务可用性和响应时间不属于程序的确定性保证。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;日常需要哪些 vcpkg 操作&lt;a href=&quot;#日常需要哪些-vcpkg-操作&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我最后留下的是一组很短的操作表：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 搜索 registry 中的 port&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cpr&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 向 manifest 添加直接依赖&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;port&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cpr&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 按当前 manifest 手动安装&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 更新 registry baseline&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x-update-baseline&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 查看 triplet 帮助&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vcpkg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;triplet&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;在 Manifest mode 中，移除依赖的干净做法是从 &lt;code&gt;vcpkg.json&lt;/code&gt; 的 &lt;code&gt;dependencies&lt;/code&gt; 删除它，再重新配置或运行 &lt;code&gt;vcpkg install&lt;/code&gt;，让声明成为唯一事实来源。不要一边保留 manifest 声明，一边手工删除安装树里的文件，那会把”当前状态”和”期望状态”重新拆开。&lt;/p&gt;&lt;p&gt;triplet 描述目标架构、操作系统、运行库和链接方式。常见默认值包括 &lt;code&gt;x64-windows&lt;/code&gt;、&lt;code&gt;x64-linux&lt;/code&gt; 和 &lt;code&gt;x64-osx&lt;/code&gt;。如果需要 Windows 静态链接，可以在 preset 的 &lt;code&gt;cacheVariables&lt;/code&gt; 中明确设置：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;VCPKG_TARGET_TRIPLET&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;x64-windows-static&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;改变 triplet、编译器或重要 vcpkg 配置后，我会使用新的 build 目录或删除旧 CMake cache 再配置。CMake cache 记住了第一次配置的工具链；试图在同一个目录里把整套 ABI 偷梁换柱，通常只会得到一段很有教育意义的错误日志。&lt;/p&gt;&lt;p&gt;vcpkg 默认还会使用 binary cache。第一次从源码构建后，它可以按 ABI hash 复用已构建包；CI 或团队也能通过 &lt;code&gt;VCPKG_BINARY_SOURCES&lt;/code&gt; 配置共享文件、HTTP、NuGet 等后端。对这个小项目，本地默认 cache 已经够用。等构建 OpenSSL 的次数开始按团队人数相乘时，再配置共享 cache，收益会非常具体。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我实际踩到的坑&lt;a href=&quot;#我实际踩到的坑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;find_package()&lt;/code&gt; 找不到库。&lt;/strong&gt; 最常见原因不是库名拼错，而是配置阶段根本没有加载 vcpkg toolchain。检查 &lt;code&gt;VCPKG_ROOT&lt;/code&gt;、preset 展开结果和 build 目录，不要靠全局 &lt;code&gt;include_directories()&lt;/code&gt; 把错误暂时盖住。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;下载失败不等于编译失败。&lt;/strong&gt; 我第一次拉取 CPR 和 fmt 源码时遇到 &lt;code&gt;curl error 56&lt;/code&gt;。重新下载后，vcpkg 继续使用已经完成的 port 和 binary cache，不需要从 0 重建全部 10 个节点。网络、port 构建和项目编译是 3 个阶段，先看失败发生在哪一层。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;不要猜 CMake target 名。&lt;/strong&gt; port 安装完成后，vcpkg 会打印 usage，例如 &lt;code&gt;find_package(cpr CONFIG REQUIRED)&lt;/code&gt; 和 &lt;code&gt;target_link_libraries(... cpr::cpr)&lt;/code&gt;。包名、CMake package 名和 target 名不保证大小写或拼写完全一致，安装输出和 port 文档比直觉可靠。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;不要把 toolchain 路径硬编码进项目。&lt;/strong&gt; 本机的 &lt;code&gt;C:\tools\vcpkg&lt;/code&gt; 对另一台机器没有意义。共享 preset 引用 &lt;code&gt;$env{VCPKG_ROOT}&lt;/code&gt;，本机差异放环境变量或 &lt;code&gt;CMakeUserPresets.json&lt;/code&gt;，边界会清楚很多。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;再往前一步&lt;a href=&quot;#再往前一步&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这次实验最有价值的产物其实不是 100 多行 C++，而是一条可以重复的链路：&lt;code&gt;vcpkg.json&lt;/code&gt; 声明依赖，baseline 固定 registry 视图，triplet 描述目标 ABI，vcpkg toolchain 把安装树暴露给 CMake，imported target 再把 usage requirements 传给 &lt;code&gt;vhttp&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;我以后写桌面工具、网络服务或需要第三方库的嵌入式上位机时，大概率会继续沿用这套结构。项目变大后，可以增加自定义 triplet、manifest feature、overlay port 和共享 binary cache，但核心仍然是这 5 个文件和两条 CMake 命令。C++ 仍然没有突然长出一个随语言发行的 Cargo，不过现在至少依赖不再散落在下载目录和项目属性对话框里。很好，我可以继续写 C++ 了。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/vcpkg/get_started/get-started&quot; target=&quot;_blank&quot;&gt;vcpkg 官方入门: Install and use packages with CMake&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/vcpkg/concepts/manifest-mode&quot; target=&quot;_blank&quot;&gt;vcpkg Manifest mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/vcpkg/users/buildsystems/cmake-integration&quot; target=&quot;_blank&quot;&gt;vcpkg 与 CMake 集成&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/vcpkg/users/versioning&quot; target=&quot;_blank&quot;&gt;vcpkg Versioning reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/vcpkg/concepts/triplets&quot; target=&quot;_blank&quot;&gt;vcpkg Triplets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/libcpr/cpr&quot; target=&quot;_blank&quot;&gt;CPR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/CLIUtils/CLI11&quot; target=&quot;_blank&quot;&gt;CLI11&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/gabime/spdlog&quot; target=&quot;_blank&quot;&gt;spdlog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded></item><item><title>npm 与 pnpm 安装的到底是什么？从 JavaScript 包到原生模块</title><link>https://bfmhno3.github.io/posts/npm-pnpm-tooling-guide/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/npm-pnpm-tooling-guide/</guid><description>我原以为 npm 和 pnpm 安装的只是 JavaScript 或 TypeScript 包，直到遇到 sharp 这类包含原生模块和平台产物的依赖。本文在 WSL2 中动手实验，沿着 registry、lockfile、store、node_modules 和 CLI 入口把这件事弄清楚。</description><pubDate>Fri, 28 Aug 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我一开始也以为 npm 和 pnpm 安装的就是 JavaScript 或 TypeScript 包，最多再附带几段配置文件。直到我见到的包越来越多，尤其是 &lt;code&gt;sharp&lt;/code&gt; 这类会带上原生 &lt;code&gt;.node&lt;/code&gt; 模块、预编译二进制和平台依赖的包，这个模型终于不够用了。于是我在 WSL2 里建了一个隔离项目，沿着 registry、tarball、lockfile、store 和 &lt;code&gt;node_modules&lt;/code&gt; 一路观察：我们安装的到底是什么，最后又是谁把它变成了一个可以执行的命令。&lt;/p&gt;
&lt;p&gt;本文以 WSL2 中的 Ubuntu 22.04 LTS 为例，命令行提示符统一使用虚构的 &lt;code&gt;user@wsl&lt;/code&gt;。路径中的用户名、主机名、硬件、IP、Windows 路径和日志路径均已省略；示例版本和下载数量也会随时间变化，不要把它们当成自己的环境事实。&lt;/p&gt;
&lt;p&gt;我会先建立一份隔离实验，再回头解释每个文件和命令为什么出现。这样比背安装咒语可靠得多：包管理器就像仓库管理员，lockfile 是出入库账本，&lt;code&gt;node_modules&lt;/code&gt; 则是当前项目真正拿来工作的装配区。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;npm、pnpm、Node.js 和 nvm 分别是什么&lt;a href=&quot;#npmpnpmnodejs-和-nvm-分别是什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node.js&lt;/strong&gt; 是 JavaScript 的运行时，负责执行 JavaScript 程序。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;npm&lt;/strong&gt; 是 Node.js 生态的包管理器，通常随 Node.js 一起安装。它负责下载依赖、维护 &lt;code&gt;package.json&lt;/code&gt; 和 &lt;code&gt;package-lock.json&lt;/code&gt;，也能执行项目脚本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pnpm&lt;/strong&gt; 是独立的 Node.js 包管理器。它与 npm 使用同一个 npm registry 生态，但命令、锁文件和 &lt;code&gt;node_modules&lt;/code&gt; 的组织方式有所不同。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;nvm&lt;/strong&gt; 是 Node.js 版本管理器，用来安装和切换 Node.js 版本。它不是包管理器，也不负责安装项目依赖。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Corepack&lt;/strong&gt; 是包管理器入口和版本准备工具。它可以根据项目声明准备指定版本的 pnpm 或 Yarn，但它不是 npm，也不负责安装项目依赖。&lt;/li&gt;
&lt;/ul&gt;&lt;section&gt;&lt;h3&gt;npm 和 pnpm 怎么选&lt;a href=&quot;#npm-和-pnpm-怎么选&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;两者都能创建项目、安装依赖和执行脚本，但默认行为不同：&lt;/p&gt;







































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;对比项&lt;/th&gt;&lt;th&gt;npm&lt;/th&gt;&lt;th&gt;pnpm&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;通常来源&lt;/td&gt;&lt;td&gt;随 Node.js 发行版提供&lt;/td&gt;&lt;td&gt;独立安装或由 Corepack 准备&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;锁文件&lt;/td&gt;&lt;td&gt;&lt;code&gt;package-lock.json&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;pnpm-lock.yaml&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;默认依赖布局&lt;/td&gt;&lt;td&gt;直接组织在项目 &lt;code&gt;node_modules&lt;/code&gt; 中&lt;/td&gt;&lt;td&gt;共享 store，加 virtual store 和链接&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;磁盘复用&lt;/td&gt;&lt;td&gt;依赖具体版本和配置&lt;/td&gt;&lt;td&gt;共享内容寻址 store，通常更节省空间&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;依赖声明约束&lt;/td&gt;&lt;td&gt;相对宽松&lt;/td&gt;&lt;td&gt;链接布局更容易暴露未声明依赖&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;添加依赖&lt;/td&gt;&lt;td&gt;&lt;code&gt;npm install &amp;lt;pkg&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;pnpm add &amp;lt;pkg&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;推荐规则很简单：&lt;strong&gt;新项目优先考虑 pnpm，已有项目必须遵循项目声明。&lt;/strong&gt; 如果项目已经提交了 &lt;code&gt;package-lock.json&lt;/code&gt;，通常使用 npm；如果项目已经提交了 &lt;code&gt;pnpm-lock.yaml&lt;/code&gt;，就使用 pnpm。不要在同一个项目里交替执行 &lt;code&gt;npm install&lt;/code&gt; 和 &lt;code&gt;pnpm install&lt;/code&gt;，这样会产生两套锁文件和难以解释的依赖变化。&lt;/p&gt;&lt;p&gt;pnpm 并不是所有场景的唯一答案。只想快速开始一个小项目，或者参与明确要求 npm 的项目时，npm 更直接。选择的关键不是“哪个命令更酷”，而是项目是否有明确的包管理器契约，以及团队是否统一使用它。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;在 WSL2 中准备 Node.js&lt;a href=&quot;#在-wsl2-中准备-nodejs&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;进入 Ubuntu 后，可以先确认系统和 shell。以下输出只保留与教程有关的部分：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;uname&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-sr&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Linux&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;6.x.x-microsoft-standard-WSL2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;head&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GNU&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;version&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;5.1.16&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;使用 nvm 安装 Node.js。安装远程脚本前应先阅读来源和内容，因为 &lt;code&gt;curl | bash&lt;/code&gt; 会直接执行下载的脚本：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-o-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.7/install.sh&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;Downloading&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nvm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;$HOME/.nvm&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;Appending&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nvm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;$HOME/.bashrc&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;当前 shell 立即加载 nvm：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$HOME&lt;/span&gt;&lt;span&gt;/.nvm/nvm.sh&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nvm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;24&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Downloading&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;installing&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v24.20.0...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Computing&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;checksum&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sha256sum&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Checksums&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;matched!&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Now&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v24.20.0&lt;/span&gt;&lt;span&gt; (npm &lt;/span&gt;&lt;span&gt;v11.19.0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Creating&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;alias:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; -&amp;gt; &lt;/span&gt;&lt;span&gt;24&lt;/span&gt;&lt;span&gt; (-&amp;gt; &lt;/span&gt;&lt;span&gt;v24.20.0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;v24.20.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;11.19.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里的 &lt;code&gt;npm&lt;/code&gt; 已经随 Node.js 一起提供。此时可以使用 npm，但还没有因为安装 Node.js 就自动得到 pnpm。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Corepack 到底做什么&lt;a href=&quot;#corepack-到底做什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Corepack 最容易被误解成“另一个包管理器”。更准确的分层是：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;Node.js 执行 JavaScript。&lt;/li&gt;
&lt;li&gt;npm 是一个包管理器，通常随 Node.js 分发。&lt;/li&gt;
&lt;li&gt;pnpm 和 Yarn 是其他包管理器。&lt;/li&gt;
&lt;li&gt;Corepack 是包管理器版本管理器，为 pnpm、Yarn 等准备版本并提供转发入口。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Corepack 解决的问题是：&lt;strong&gt;应该运行哪个版本的包管理器&lt;/strong&gt;。它不解决：&lt;strong&gt;项目应该安装哪些依赖&lt;/strong&gt;。后一个问题仍由 &lt;code&gt;pnpm install&lt;/code&gt;、&lt;code&gt;pnpm add&lt;/code&gt;、&lt;code&gt;npm install&lt;/code&gt; 等命令负责。&lt;/p&gt;&lt;p&gt;这里有一个容易随 Node.js 版本变化的现实：不能假设每个 Node.js 发行版都内置 Corepack。检查命令是否存在；如果不存在，按项目文档安装 pnpm，或者使用 pnpm 官方提供的安装方式。无论采用哪种方式，最后都要检查实际运行的 &lt;code&gt;pnpm --version&lt;/code&gt;，而不是只检查安装命令是否成功。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;corepack enable pnpm&lt;/code&gt; 发生了什么&lt;a href=&quot;#corepack-enable-pnpm-发生了什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在提供 Corepack 的 Node.js 环境中执行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;corepack&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;0.35.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;corepack&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;enable&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.../bin/pnpm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;corepack enable pnpm&lt;/code&gt; 通常会创建或启用一个名为 &lt;code&gt;pnpm&lt;/code&gt; 的 shim。shim 不是完整的 pnpm，它更像一个转接头：用户执行 &lt;code&gt;pnpm&lt;/code&gt; 时，shim 把请求交给 Corepack；Corepack 再准备合适版本的 pnpm 并转发参数。&lt;/p&gt;&lt;p&gt;因此第一次执行 &lt;code&gt;pnpm&lt;/code&gt; 时，可能看到下载确认：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;! &lt;/span&gt;&lt;span&gt;Corepack&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;about&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;download&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://registry.npmjs.org/pnpm/-/pnpm-11.24.0.tgz&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;? Do you want to continue? [Y/n] y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;11.24.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这次下载的是 &lt;strong&gt;pnpm 自己&lt;/strong&gt;，不是项目依赖。确认下载后，Corepack 保存或复用这个包，之后继续把 &lt;code&gt;pnpm&lt;/code&gt; 命令交给它执行。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;packageManager&lt;/code&gt; 和 Corepack 的关系&lt;a href=&quot;#packagemanager-和-corepack-的关系&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;项目可以在 &lt;code&gt;package.json&lt;/code&gt; 中写：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;packageManager&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;pnpm@11.22.0&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这个字段是项目声明，意思是“处理这个项目时使用 pnpm 11.22.0”。它不是依赖声明，也不会把 &lt;code&gt;pnpm&lt;/code&gt; 写入项目的 &lt;code&gt;dependencies&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;如果项目的 &lt;code&gt;.npmrc&lt;/code&gt; 还包含：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;manage-package-manager-versions&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;那么 pnpm 可以根据 &lt;code&gt;packageManager&lt;/code&gt; 字段参与包管理器版本管理。可以把两者理解为：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;packageManager&lt;/code&gt;：项目声明应该使用什么工具和版本。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;manage-package-manager-versions&lt;/code&gt;：pnpm 是否允许按这项声明管理工具版本。&lt;/li&gt;
&lt;li&gt;Corepack shim：让命令能够进入版本准备和转发流程。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pnpm install&lt;/code&gt;：真正安装项目依赖。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这个配置键是 pnpm 的设置，不是 npm 的通用配置。若同一目录还会运行 npm，npm 可能提示 &lt;code&gt;Unknown project config&lt;/code&gt;；这不是安装失败，但说明不应把 pnpm 专属选项当成跨工具协议。项目最好只保留实际使用的包管理器配置，并把团队约定写进项目文档或自动检查中。&lt;/p&gt;&lt;p&gt;检查四个版本时，命令和输出应分别理解：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;v24.20.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;11.19.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;corepack&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;0.35.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;11.24.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如果某个 Node.js 发行版没有提供 Corepack，&lt;code&gt;corepack&lt;/code&gt; 命令可能不存在。这时应按照项目文档安装 pnpm，最后仍然用 &lt;code&gt;pnpm --version&lt;/code&gt; 检查实际运行版本。全局安装 pnpm、Corepack 提供命令入口、&lt;code&gt;pnpm add&lt;/code&gt; 安装项目依赖，是三个不同动作。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;不要把 &lt;code&gt;corepack enable&lt;/code&gt;、全局安装 pnpm 和 &lt;code&gt;pnpm add&lt;/code&gt; 混成一个动作。前两者处理“命令从哪里来、运行哪个版本”，最后一个才修改当前项目的依赖。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;用隔离项目观察安装过程&lt;a href=&quot;#用隔离项目观察安装过程&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;不要为了学习包管理器直接修改重要项目。创建临时目录后，在其中安装几个形态不同的包：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tmp_dir=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;mktemp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$tmp_dir&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/tmp/tmp.xxxxxxxx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$tmp_dir&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Wrote&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/tmp/tmp.xxxxxxxx/package.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lodash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;typescript&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sharp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Packages:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+9&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Progress:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;resolved&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;54,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reused&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;downloaded&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;12,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;added&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;9,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;done&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dependencies:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lodash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4.18.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sharp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0.35.4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;typescript&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;7.0.2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Done&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;6.5s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v11.24.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这里的版本和下载数量会变化。重要的是动作：&lt;code&gt;pnpm init&lt;/code&gt; 创建 &lt;code&gt;package.json&lt;/code&gt;，&lt;code&gt;pnpm add&lt;/code&gt; 修改依赖声明、解析依赖并创建安装目录。&lt;/p&gt;&lt;p&gt;查看生成的声明：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;tmp-project&quot;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;version&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;1.0.0&quot;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;packageManager&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;pnpm@11.24.0&quot;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;type&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;module&quot;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;lodash&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;^4.18.1&quot;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;sharp&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;^0.35.4&quot;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;typescript&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;^7.0.2&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;pnpm init&lt;/code&gt; 在不同版本中生成的字段可能不同。不要把临时项目的名字、版本或格式当成固定模板。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;用 npm 做一次对照&lt;a href=&quot;#用-npm-做一次对照&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;我会再开一个目录，只安装一个开发工具。这样实验不会把 npm 和 pnpm 的文件混在一起：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;npm_dir=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;mktemp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:~$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$npm_dir&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.yyyyyyyy$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-y&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Wrote&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/tmp/tmp.yyyyyyyy/package.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.yyyyyyyy$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--save-dev&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;typescript&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;added&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audited&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;packages&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1s&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.yyyyyyyy$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tsc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Version&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;7.0.2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这个目录会得到 &lt;code&gt;package-lock.json&lt;/code&gt;，而不是 &lt;code&gt;pnpm-lock.yaml&lt;/code&gt;。&lt;code&gt;npm exec&lt;/code&gt; 和 &lt;code&gt;pnpm exec&lt;/code&gt; 都优先使用项目本地的 CLI，但两者不会共享锁文件格式，也不应该在同一个项目里轮流负责更新依赖。这个对照实验的价值不在于哪一个命令少敲两个字符，而在于把“同一个 registry 生态”和“不同的项目安装实现”分开观察。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;包不是语言&lt;a href=&quot;#包不是语言&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;registry 发布的是带元数据的 tarball，而不是“一个 JavaScript 文件”。包中可以包含：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;JavaScript 或 TypeScript 源码&lt;/li&gt;
&lt;li&gt;WASM 文件&lt;/li&gt;
&lt;li&gt;原生 &lt;code&gt;.node&lt;/code&gt; 模块&lt;/li&gt;
&lt;li&gt;预编译二进制&lt;/li&gt;
&lt;li&gt;CLI 入口&lt;/li&gt;
&lt;li&gt;其他包的运行时或平台依赖&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;实验中的三个包分别说明了不同情况：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lodash&lt;/code&gt; 主要作为可导入的 JavaScript API 使用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;typescript&lt;/code&gt; 提供 API，也通过 &lt;code&gt;bin&lt;/code&gt; 字段提供 &lt;code&gt;tsc&lt;/code&gt; 命令。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sharp&lt;/code&gt; 会根据操作系统和 CPU 解析平台相关实现，安装时可能出现 &lt;code&gt;@img/*&lt;/code&gt; 等平台包。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;查询 registry 元数据：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;view&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lodash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dist.tarball&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;view&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;typescript&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;tsc&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;bin/tsc&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;bin&lt;/code&gt; 字段表示包声明了命令入口。安装后，包管理器会在项目中创建 &lt;code&gt;node_modules/.bin/tsc&lt;/code&gt; 的链接或 shim：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tsc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Version&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;7.0.2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;tsc&lt;/code&gt; 不是 shell 自带命令，也不是独立安装的系统程序。它来自 TypeScript 包的 &lt;code&gt;bin&lt;/code&gt; 声明，最终由 Node.js 执行。判断一个包能做什么，应查看它的 &lt;code&gt;exports&lt;/code&gt;、&lt;code&gt;main&lt;/code&gt;、&lt;code&gt;bin&lt;/code&gt;、平台依赖和安装脚本，而不是只看包名。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一次安装改变了哪些东西&lt;a href=&quot;#一次安装改变了哪些东西&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;可以把项目依赖分成三层：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;声明层&lt;/strong&gt;：&lt;code&gt;package.json&lt;/code&gt; 中的 &lt;code&gt;dependencies&lt;/code&gt; 和 &lt;code&gt;devDependencies&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解析层&lt;/strong&gt;：lockfile 中的实际版本、间接依赖、peer dependency 和完整性信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;落盘层&lt;/strong&gt;：store、virtual store、&lt;code&gt;node_modules&lt;/code&gt; 链接和 &lt;code&gt;.bin&lt;/code&gt; 入口。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;运行 &lt;code&gt;pnpm add lodash&lt;/code&gt; 时，大致发生四步：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;读取项目边界和 registry 配置。&lt;/li&gt;
&lt;li&gt;查询包元数据，按照 semver 范围选择版本并写入 lockfile。&lt;/li&gt;
&lt;li&gt;下载 tarball，校验后写入内容寻址 store。&lt;/li&gt;
&lt;li&gt;在项目中创建依赖链接和命令入口。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;用命令查看这些层：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;root&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/tmp/tmp.xxxxxxxx/node_modules&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;store&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;home-directory&amp;gt;/.local/share/pnpm/store/v11&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;realpath&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;node_modules/lodash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/tmp/tmp.xxxxxxxx/node_modules/.pnpm/lodash@4.18.1/node_modules/lodash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;why&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sharp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;sharp@0.35.4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tmp-project@1.0.0&lt;/span&gt;&lt;span&gt; (dependencies)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;pnpm 的 store 像共享零件仓，&lt;code&gt;node_modules/.pnpm&lt;/code&gt; 像当前项目的装配区，顶层 &lt;code&gt;node_modules/lodash&lt;/code&gt; 是给 Node.js 看的入口。多个项目可以复用 store，但项目自己的 &lt;code&gt;package.json&lt;/code&gt;、lockfile、链接和 peer 组合仍然独立。&lt;/p&gt;&lt;p&gt;npm 的目标相同，但默认的依赖布局不同。应用代码应依赖包公开的 &lt;code&gt;exports&lt;/code&gt; 或 &lt;code&gt;main&lt;/code&gt;，不要依赖某个包管理器偶然生成的内部路径。&lt;/p&gt;&lt;p&gt;&lt;code&gt;^1.2.3&lt;/code&gt; 是版本范围，不是唯一版本。它允许一定范围内的更新；lockfile 则记录这次实际选择的版本。&lt;code&gt;pnpm install --frozen-lockfile&lt;/code&gt; 会拒绝在安装时偷偷修改 lockfile，适合 CI 和新机器：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/path/to/project$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--frozen-lockfile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Lockfile&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;up&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;date,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;resolution&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;step&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;skipped&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Already&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;up&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;npm 项目通常使用 &lt;code&gt;npm ci&lt;/code&gt; 达到类似的“按已有锁文件安装”效果。&lt;code&gt;pnpm install&lt;/code&gt; 和 &lt;code&gt;pnpm update&lt;/code&gt; 也不是一回事：前者按当前声明安装，后者主动寻找允许范围内的新版本。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;命令为什么能运行&lt;a href=&quot;#命令为什么能运行&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;普通 shell 通过 &lt;code&gt;PATH&lt;/code&gt; 查找命令：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;node-installation&amp;gt;/bin/node&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;node-installation&amp;gt;/bin/pnpm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;执行项目脚本时，npm 和 pnpm 会临时把项目的 &lt;code&gt;node_modules/.bin&lt;/code&gt; 放到脚本环境的 PATH 前面。因此，安装 TypeScript 后可以在 &lt;code&gt;package.json&lt;/code&gt; 中定义：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;type-check&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tsc --version&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;普通 shell 不会自动注入这层 PATH。可以通过以下两种入口明确执行同一个本地 &lt;code&gt;tsc&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/path/to/project$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tsc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Version&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;7.0.2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/path/to/project$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;node_modules/.bin/tsc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Version&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;7.0.2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;四种常见入口可以这样区分：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;node src/index.mjs&lt;/code&gt;：Node.js 直接运行文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pnpm exec tsc --version&lt;/code&gt;：从当前项目依赖中寻找并运行 &lt;code&gt;tsc&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pnpm run dev&lt;/code&gt;：读取 &lt;code&gt;package.json&lt;/code&gt; 的 &lt;code&gt;scripts&lt;/code&gt;，并注入本地 &lt;code&gt;.bin&lt;/code&gt; PATH。&lt;/li&gt;
&lt;li&gt;脚本启动 Node.js、shell 或其他子进程：子进程继承脚本环境。&lt;/li&gt;
&lt;/ol&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;npm run&lt;/code&gt;、&lt;code&gt;pnpm run&lt;/code&gt; 和直接执行&lt;a href=&quot;#npm-runpnpm-run-和直接执行&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;npm run build&lt;/code&gt; 与 &lt;code&gt;pnpm run build&lt;/code&gt; 都会读取 &lt;code&gt;package.json&lt;/code&gt; 的 &lt;code&gt;scripts&lt;/code&gt; 字段，并把项目的 &lt;code&gt;node_modules/.bin&lt;/code&gt; 放到脚本环境的 &lt;code&gt;PATH&lt;/code&gt; 前面。脚本因此可以直接调用本地安装的 &lt;code&gt;tsc&lt;/code&gt;、&lt;code&gt;astro&lt;/code&gt; 或 &lt;code&gt;vite&lt;/code&gt;，不需要用户把它们全局安装一份。&lt;/p&gt;&lt;p&gt;我通常把入口分成三类：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;node src/index.mjs&lt;/code&gt;：Node.js 直接运行文件，完全绕开包管理器脚本。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pnpm exec tsc --version&lt;/code&gt;：从当前项目依赖中寻找并运行本地 CLI。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pnpm run type-check&lt;/code&gt;：读取脚本定义，并注入 &lt;code&gt;.bin&lt;/code&gt; PATH；npm 项目对应 &lt;code&gt;npm run type-check&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;code&gt;pnpm dlx&lt;/code&gt;、&lt;code&gt;npx&lt;/code&gt; 和 &lt;code&gt;npm exec&lt;/code&gt; 适合一次性获取并执行 CLI。它们的便利之处也是风险所在：命令可能触发一次临时下载，且不会把工具记录为项目依赖。需要可复现的工具，应写入 &lt;code&gt;devDependencies&lt;/code&gt;，再通过脚本或 &lt;code&gt;pnpm exec&lt;/code&gt; 使用。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;registry、换源和网络故障&lt;a href=&quot;#registry换源和网络故障&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;registry 是提供包元数据和 tarball 的 HTTP 服务。它可以是 npm 默认 registry、第三方镜像、公司私有 registry 或代理缓存。切换 registry 只改变查询和下载的来源，不改变项目依赖目录，也不会自动修复错误的版本范围。&lt;/p&gt;&lt;p&gt;我会先检查当前配置，而不是直接删除缓存：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registry&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;https://registry.npmjs.org/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registry&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;https://registry.npmjs.org/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;只验证一次时，使用命令行参数，不改变配置文件：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;view&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lodash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;version&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--registry=https://registry.npmjs.org/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;4.18.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;项目级 &lt;code&gt;.npmrc&lt;/code&gt; 适合项目配置，用户级配置适合个人默认值。认证信息不要写死在仓库中：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;//registry.example.invalid/:&lt;/span&gt;&lt;span&gt;_authToken&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;${NPM_TOKEN}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;遇到 &lt;code&gt;ERR_PNPM_NO_MATCHING_VERSION&lt;/code&gt;，先检查包名、registry 和可用版本，再检查 semver、lockfile 和平台限制。遇到网络超时或证书错误，依次检查 registry、DNS、代理、系统时间和 CA。不要把清空整个缓存当成万能修复。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;audit 与生命周期脚本&lt;a href=&quot;#audit-与生命周期脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;audit&lt;/code&gt; 会把依赖树中的包名和版本与已知漏洞公告进行匹配。它不审查业务代码，也不能证明没有报告的包绝对安全：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;No&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;known&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;vulnerabilities&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;found&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;user@wsl:/tmp/tmp.xxxxxxxx$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--prod&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;No&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;known&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;vulnerabilities&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;found&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;安装依赖本身也可能执行代码。&lt;code&gt;preinstall&lt;/code&gt;、&lt;code&gt;install&lt;/code&gt;、&lt;code&gt;postinstall&lt;/code&gt; 和 &lt;code&gt;prepare&lt;/code&gt; 可能编译 native addon、下载平台产物、生成代码，甚至执行任意脚本。安装前应审查包来源和生命周期脚本；在 CI 中可以评估 &lt;code&gt;--ignore-scripts&lt;/code&gt; 是否适合项目，但不要把关闭安全检查当成第一步修复。&lt;/p&gt;&lt;p&gt;如果项目的 &lt;code&gt;package.json&lt;/code&gt; 声明了 &lt;code&gt;devEngines.packageManager&lt;/code&gt;，却使用了错误的包管理器，npm 可能报：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;npm error code EBADDEVENGINES&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;npm error Invalid name &quot;pnpm&quot; does not match &quot;npm&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这表示当前 npm 不符合项目要求，不是一次漏洞扫描结果。正确做法是切换到项目声明的 pnpm，而不是删除检查或绕过它。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;从零建立可迁移环境&lt;a href=&quot;#从零建立可迁移环境&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;新机器可以按以下顺序操作：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;在 WSL2 中准备 Ubuntu 和 shell。&lt;/li&gt;
&lt;li&gt;使用 nvm 安装满足项目 &lt;code&gt;engines&lt;/code&gt; 的 Node.js，并确认 shell 启动文件已加载 nvm。&lt;/li&gt;
&lt;li&gt;检查 &lt;code&gt;node --version&lt;/code&gt; 和 &lt;code&gt;npm --version&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;启用或安装项目规定的 pnpm，并检查 &lt;code&gt;corepack --version&lt;/code&gt;（若存在）和 &lt;code&gt;pnpm --version&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;进入项目根目录，先阅读 &lt;code&gt;package.json&lt;/code&gt; 的 &lt;code&gt;packageManager&lt;/code&gt;、&lt;code&gt;engines&lt;/code&gt; 和 lockfile 类型。&lt;/li&gt;
&lt;li&gt;执行项目规定的锁定安装：pnpm 项目使用 &lt;code&gt;pnpm install --frozen-lockfile&lt;/code&gt;，npm 项目通常使用 &lt;code&gt;npm ci&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;按项目文档执行开发、检查、构建和预览命令。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;如果项目位于 &lt;code&gt;/mnt/c&lt;/code&gt; 或其他 Windows 挂载路径，安装和大量小文件访问可能明显慢于 Linux 文件系统中的 &lt;code&gt;~/projects&lt;/code&gt;。这是 I/O 边界的代价，不是 pnpm store 损坏；我通常把源码和 store 放在 WSL2 的 Linux 文件系统里，只通过编辑器或 Git 与 Windows 侧协作。&lt;/p&gt;&lt;p&gt;最小排错表：&lt;/p&gt;


























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;症状&lt;/th&gt;&lt;th&gt;先查什么&lt;/th&gt;&lt;th&gt;最小修复&lt;/th&gt;&lt;th&gt;如何确认&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;command not found&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;command -v pnpm&lt;/code&gt;、&lt;code&gt;echo $PATH&lt;/code&gt;&lt;/td&gt;&lt;td&gt;修正 PATH 或 Corepack 入口&lt;/td&gt;&lt;td&gt;&lt;code&gt;pnpm --version&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ERR_PNPM_NO_MATCHING_VERSION&lt;/code&gt;&lt;/td&gt;&lt;td&gt;包名、registry、可用版本&lt;/td&gt;&lt;td&gt;修正版本范围或 registry&lt;/td&gt;&lt;td&gt;&lt;code&gt;pnpm view &amp;lt;pkg&amp;gt; versions&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;registry 超时或证书错误&lt;/td&gt;&lt;td&gt;registry、代理、系统时间&lt;/td&gt;&lt;td&gt;修正源、DNS、代理或 CA&lt;/td&gt;&lt;td&gt;单次 &lt;code&gt;pnpm view&lt;/code&gt; 成功&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;lockfile 不同步&lt;/td&gt;&lt;td&gt;&lt;code&gt;git diff package.json pnpm-lock.yaml&lt;/code&gt;&lt;/td&gt;&lt;td&gt;使用正确管理器更新锁文件&lt;/td&gt;&lt;td&gt;&lt;code&gt;pnpm install --frozen-lockfile&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Node 不满足 engines&lt;/td&gt;&lt;td&gt;&lt;code&gt;node --version&lt;/code&gt;&lt;/td&gt;&lt;td&gt;切换 Node.js 版本&lt;/td&gt;&lt;td&gt;版本满足 &lt;code&gt;engines&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;native 模块安装失败&lt;/td&gt;&lt;td&gt;&lt;code&gt;pnpm why &amp;lt;pkg&amp;gt;&lt;/code&gt;、平台包和安装日志&lt;/td&gt;&lt;td&gt;修复平台依赖或编译链&lt;/td&gt;&lt;td&gt;实际导入模块&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;脚本找不到本地 CLI&lt;/td&gt;&lt;td&gt;&lt;code&gt;pnpm exec &amp;lt;cmd&amp;gt;&lt;/code&gt;、脚本 PATH&lt;/td&gt;&lt;td&gt;使用项目脚本或 &lt;code&gt;pnpm exec&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&amp;lt;cmd&amp;gt; --version&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;EBADDEVENGINES&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;packageManager&lt;/code&gt;、当前包管理器&lt;/td&gt;&lt;td&gt;使用项目声明的管理器&lt;/td&gt;&lt;td&gt;&lt;code&gt;pnpm --version&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;整条路径可以概括为：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Shell PATH&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;npm 或 pnpm&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;registry 元数据&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;tarball&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pnpm store&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;virtual store&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;node_modules 链接&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;.bin shim&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Node.js 进程&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;package.json scripts&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Shell PATH&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;npm 或 pnpm&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;registry 元数据&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;tarball&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;pnpm store&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;virtual store&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;node_modules 链接&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;.bin shim&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;Node.js 进程&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;package.json scripts&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;真正值得记住的不是一串安装咒语，而是这条边界链：包从哪个 registry 来，版本解析记录在哪里，文件落在哪一层，命令由哪个入口转给哪个 Node.js。顺着这条链排查，npm 和 pnpm 的差异就会变成可以观察和验证的实现选择。未来包管理器大概还会继续把“下载一个包”包装成更多自动化，但 lockfile、脚本和运行时边界不会凭空消失；理解它们，至少能让下一次 &lt;code&gt;command not found&lt;/code&gt; 变成一个可定位的实验，而不是一次祈祷。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.npmjs.com/cli/v11/configuring-npm/package-json&quot; target=&quot;_blank&quot;&gt;npm package.json 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.npmjs.com/cli/v11/using-npm/scripts&quot; target=&quot;_blank&quot;&gt;npm scripts 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.npmjs.com/auditing-package-dependencies-for-security-vulnerabilities&quot; target=&quot;_blank&quot;&gt;npm audit 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pnpm.io/cli/add&quot; target=&quot;_blank&quot;&gt;pnpm add 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pnpm.io/package_json&quot; target=&quot;_blank&quot;&gt;pnpm package.json 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pnpm.io/cli/store&quot; target=&quot;_blank&quot;&gt;pnpm store 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pnpm.io/settings/node-modules&quot; target=&quot;_blank&quot;&gt;pnpm Node-Modules 设置&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nodejs.org/api/corepack.html&quot; target=&quot;_blank&quot;&gt;Node.js Corepack 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nodejs.org/en/learn/getting-started/an-introduction-to-the-npm-package-manager&quot; target=&quot;_blank&quot;&gt;Node.js npm 入门指南&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded></item><item><title>Cudy TR3000 256 MB 刷入 ImmortalWrt：一次完整的图形化流程</title><link>https://bfmhno3.github.io/posts/cudy-tr3000-immortalwrt-flashing-guide/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/cudy-tr3000-immortalwrt-flashing-guide/</guid><description>记录 Cudy TR3000 256 MB V1 从原厂固件、签名中间固件到 ImmortalWrt 25.12.1 和自定义镜像的完整图形化刷机流程，并介绍官方 TFTP 恢复方法。</description><pubDate>Thu, 27 Aug 2026 01:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;刷机风险提示&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;以下风险提示来自 Cudy 官方京东自营店，具体情况请以官方最新说明为准：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;刷机后遇到的相关问题，需您自主寻求解决方案，店铺不提供技术支持。&lt;/li&gt;
&lt;li&gt;刷机后不支持退货退款，保修期内最多支持两次因刷机产生的售后。&lt;/li&gt;
&lt;li&gt;保留权益：除刷机产生的售后外，其他质量问题在保修期内仍可享受正常售后。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;本文只记录我这台 &lt;strong&gt;Cudy TR3000 256 MB V1&lt;/strong&gt; 的实际操作。刷机有风险，请确认自己能接受设备无法启动、需要恢复固件，甚至失去部分售后支持的结果。官方政策和设备支持范围可能变化，购买和操作前请以 Cudy 官方说明为准。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;我在 2025 年 9 月 1 日买过一台 Cudy TR3000 256 MB V1，刷入 ImmortalWrt 后用了接近一年，网络稳定性和日常体验都还不错。2026 年 8 月 24 日，机缘巧合之下，我又买了一台同型号设备，于是决定把这次从原厂系统到 ImmortalWrt，再到自定义镜像和官方恢复的完整过程记录下来。&lt;/p&gt;
&lt;p&gt;这次我没有拆机，也没有进入 UART 或 U-Boot。真正做的事情基本都在浏览器里完成，唯一稍微像“网络工程”的部分是最后用 Tftpd64 通过 TFTP 把官方固件送回路由器。整个过程更像一条网页里的接力赛：原厂网页先交棒给中间固件，中间固件再交棒给 ImmortalWrt，最后我还让它跑了一圈自定义构建。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先确认：这是 256 MB 版本&lt;a href=&quot;#先确认这是-256-mb-版本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这台设备背面标签右上角明确写着 &lt;strong&gt;256 MB 闪存版&lt;/strong&gt;，产品型号是 &lt;code&gt;TR3000&lt;/code&gt;，管理地址是 &lt;code&gt;192.168.10.1&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/2026-08-24-device-label.jpg&quot; alt=&quot;2026 年购买的 Cudy TR3000 256 MB V1 原始背面标签照片&quot; /&gt;&lt;figcaption&gt;2026 年购买的 Cudy TR3000 256 MB V1 原始背面标签照片&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;这台设备的 SN 前缀是 &lt;code&gt;S0001825&lt;/code&gt;。去年那台设备的 SN 前缀则是 &lt;code&gt;TR3000&lt;/code&gt;。两台机器标签形式不同，但都明确标注了 256 MB 闪存版本。本文只记录这两台 256 MB V1 设备，不把其他批次的 SN 规则推广到本设备之外。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/2025-09-01-device-label.jpg&quot; alt=&quot;2025 年购买的 Cudy TR3000 256 MB V1 原始背面标签照片&quot; /&gt;&lt;figcaption&gt;2025 年购买的 Cudy TR3000 256 MB V1 原始背面标签照片&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Important&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;文章中的文件名、界面和分区选择只适用于明确标注为 &lt;strong&gt;Cudy TR3000 256 MB V1&lt;/strong&gt; 的设备。不要只凭 SN 猜测硬件版本，应该先确认机身标签上的容量标识。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第一次开机：先把原厂网页走完&lt;a href=&quot;#第一次开机先把原厂网页走完&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我建议全程使用网线，把电脑连接到路由器的 LAN 口。开始前关闭电脑上的其他网络连接，避免电脑通过 Wi-Fi 或另一块网卡走错路由。刷写过程中保持 USB-C 供电稳定，不要碰电源线。&lt;/p&gt;&lt;p&gt;打开：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;http://192.168.10.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;第一次登录时，原厂页面会要求创建管理员密码。我这里设置成了 &lt;code&gt;00000000&lt;/code&gt;，只是为了演示方便；实际使用请设置一个强密码，并在发布截图前自行处理其中的敏感信息。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/01-create-admin-password.jpg&quot; alt=&quot;原厂首次登录时创建管理员密码的页面&quot; /&gt;&lt;figcaption&gt;原厂首次登录时创建管理员密码的页面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;设置密码后，系统会进入快速设置引导。我按下面的顺序一路点击“下一步”：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;选择模式。&lt;/li&gt;
&lt;li&gt;选择时区。&lt;/li&gt;
&lt;li&gt;配置 WAN。&lt;/li&gt;
&lt;li&gt;配置无线网络。&lt;/li&gt;
&lt;li&gt;查看概要。&lt;/li&gt;
&lt;li&gt;点击“保存&amp;amp;应用”。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/02-choose-mode.jpg&quot; alt=&quot;原厂快速设置中的模式选择页面&quot; /&gt;&lt;figcaption&gt;原厂快速设置中的模式选择页面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/03-choose-timezone.jpg&quot; alt=&quot;原厂快速设置中的时区选择页面&quot; /&gt;&lt;figcaption&gt;原厂快速设置中的时区选择页面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/04-configure-wan.jpg&quot; alt=&quot;原厂快速设置中的 WAN 配置页面&quot; /&gt;&lt;figcaption&gt;原厂快速设置中的 WAN 配置页面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/05-configure-wireless.jpg&quot; alt=&quot;原厂快速设置中的无线配置页面&quot; /&gt;&lt;figcaption&gt;原厂快速设置中的无线配置页面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/06-setup-summary.jpg&quot; alt=&quot;原厂快速设置的概要页面&quot; /&gt;&lt;figcaption&gt;原厂快速设置的概要页面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;完成设置后，进入原厂系统界面。页面底部的浅灰色文字包含版权信息、硬件版本、系统版本、Cudy 官网和售后邮件地址。再进入“系统”，可以看到更完整的系统信息，两处内容能够互相对应。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/07-cudy-system-page.jpg&quot; alt=&quot;原厂系统主页，底部显示硬件版本和系统版本信息&quot; /&gt;&lt;figcaption&gt;原厂系统主页，底部显示硬件版本和系统版本信息&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/08-cudy-system-information.jpg&quot; alt=&quot;原厂系统信息页面&quot; /&gt;&lt;figcaption&gt;原厂系统信息页面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第一步：刷入签名中间固件&lt;a href=&quot;#第一步刷入签名中间固件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;原厂升级入口会检查固件签名。直接把 ImmortalWrt 镜像上传到原厂网页，结果就是报错，这并不奇怪。Cudy 官方提供了一个签名中间固件，先用它绕过原厂网页这一层限制，再从中间系统安装我们真正想用的系统。&lt;/p&gt;&lt;p&gt;我使用的是 Cudy 官方 Google Drive 文件夹中的：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;TR3000 256MB Flash V1 (not for TR3000 V1)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;其中的文件名是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cudy_tr3000-256mb-v1-sysupgrade.bin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;官方入口：&lt;a href=&quot;https://www.cudy.com/zh-CN/blogs/faq/openwrt-software-download&quot; target=&quot;_blank&quot;&gt;Cudy OpenWrt 软件和中间固件下载&lt;/a&gt;。页面底部提供了 &lt;a href=&quot;https://drive.google.com/drive/folders/1BKVarlwlNxf7uJUtRhuMGUqeCa5KpMnj&quot; target=&quot;_blank&quot;&gt;Google Drive 中间固件文件夹&lt;/a&gt;，进入 &lt;code&gt;TR3000 256MB Flash V1 (not for TR3000 V1)&lt;/code&gt; 文件夹后下载对应文件。下载后先把文件保存好，不要等到网页已经打开时才临时寻找。&lt;/p&gt;&lt;p&gt;如果直接上传未签名或不匹配的文件，原厂页面会显示错误。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/09-signature-upload-error.jpg&quot; alt=&quot;原厂网页上传不匹配固件时的报错页面&quot; /&gt;&lt;figcaption&gt;原厂网页上传不匹配固件时的报错页面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;进入“基本设置”，选择“固件升级”。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/10-cudy-firmware-upgrade.jpg&quot; alt=&quot;原厂基本设置中的固件升级入口&quot; /&gt;&lt;figcaption&gt;原厂基本设置中的固件升级入口&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;点击上传，选择 &lt;code&gt;cudy_tr3000-256mb-v1-sysupgrade.bin&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/11-upload-intermediate-firmware.jpg&quot; alt=&quot;原厂网页上传 256 MB 签名中间固件&quot; /&gt;&lt;figcaption&gt;原厂网页上传 256 MB 签名中间固件&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;点击“继续”后，页面会弹出刷写进度。此时不要关闭页面、拔掉网线或断开电源。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/12-intermediate-flashing.jpg&quot; alt=&quot;签名中间固件正在刷写&quot; /&gt;&lt;figcaption&gt;签名中间固件正在刷写&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;刷写完成后，路由器会自动重启。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/13-intermediate-rebooting.jpg&quot; alt=&quot;签名中间固件刷写完成，路由器正在重启&quot; /&gt;&lt;figcaption&gt;签名中间固件刷写完成，路由器正在重启&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;这个过程中指示灯会变红。等到路由器完成重启，指示灯恢复白色，再继续下一步。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第二步：进入中间系统&lt;a href=&quot;#第二步进入中间系统&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;原厂系统的地址是 &lt;code&gt;192.168.10.1&lt;/code&gt;，中间系统启动后地址变为：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;http://192.168.1.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;中间系统第一次登录时默认没有密码，直接点击“登录”即可。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/14-intermediate-login.jpg&quot; alt=&quot;重启后进入中间固件的登录页面&quot; /&gt;&lt;figcaption&gt;重启后进入中间固件的登录页面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;登录后可以确认当前系统已经从 Cudy 原厂系统变成了 OpenWrt 中间固件。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/15-intermediate-system-page.jpg&quot; alt=&quot;中间固件的系统主页&quot; /&gt;&lt;figcaption&gt;中间固件的系统主页&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;这里是一个很容易误判的节点：现在还没有完成 ImmortalWrt 安装，只是完成了从原厂签名系统到 OpenWrt 中间系统的过渡。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第三步：刷入 ImmortalWrt 25.12.1&lt;a href=&quot;#第三步刷入-immortalwrt-25121&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我使用 ImmortalWrt Firmware Selector 的固定地址选择设备：&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://firmware-selector.immortalwrt.org/?version=25.12.1&amp;amp;target=mediatek%2Ffilogic&amp;amp;id=cudy_tr3000-256mb-v1&quot; target=&quot;_blank&quot;&gt;https://firmware-selector.immortalwrt.org/?version=25.12.1&amp;amp;target=mediatek%2Ffilogic&amp;amp;id=cudy_tr3000-256mb-v1&lt;/a&gt;&lt;/p&gt;&lt;p&gt;页面中的设备 profile 必须是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cudy_tr3000-256mb-v1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/16-immortalwrt-firmware-selector.jpg&quot; alt=&quot;ImmortalWrt Firmware Selector 中的 256 MB 设备 profile&quot; /&gt;&lt;figcaption&gt;ImmortalWrt Firmware Selector 中的 256 MB 设备 profile&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;在下载选项中选择 &lt;strong&gt;SYSUPGRADE&lt;/strong&gt;，不要选择下面的 &lt;strong&gt;KERNEL&lt;/strong&gt;。我下载到的文件名是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;immortalwrt-25.12.1-mediatek-filogic-cudy_tr3000-256mb-v1-squashfs-sysupgrade.bin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;回到中间固件管理页面，进入“系统”，选择“备份与升级”。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/17-openwrt-flash-menu.jpg&quot; alt=&quot;中间固件中进入备份与升级的点击顺序&quot; /&gt;&lt;figcaption&gt;中间固件中进入备份与升级的点击顺序&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;点击最下方的“刷写固件…”，上传刚才下载的 ImmortalWrt &lt;code&gt;sysupgrade&lt;/code&gt; 文件。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/18-upload-immortalwrt-firmware.jpg&quot; alt=&quot;上传 ImmortalWrt 25.12.1 sysupgrade 固件&quot; /&gt;&lt;figcaption&gt;上传 ImmortalWrt 25.12.1 sysupgrade 固件&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;上传后取消勾选“保留当前配置”，再点击“继续”。我选择清除旧配置，是因为中间固件和目标系统之间并不存在值得冒险保留的配置兼容性。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/19-immortalwrt-flashing.jpg&quot; alt=&quot;ImmortalWrt 固件正在刷写&quot; /&gt;&lt;figcaption&gt;ImmortalWrt 固件正在刷写&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;刷写时指示灯会变红并闪烁。等到指示灯变白，路由器完成自动重启，就可以重新进入 ImmortalWrt。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/20-immortalwrt-system-page.jpg&quot; alt=&quot;重启后进入 ImmortalWrt 系统界面&quot; /&gt;&lt;figcaption&gt;重启后进入 ImmortalWrt 系统界面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;首次配置和软件包&lt;a href=&quot;#首次配置和软件包&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;ImmortalWrt 初次进入时，会询问是否开启“值守式系统升级”。它的含义是让系统自动检查并执行符合条件的系统升级，减少手动维护工作，但自动升级也意味着升级时机和配置变化不再完全由用户控制。&lt;/p&gt;&lt;p&gt;我在演示手动升级，所以选择“否，禁用检查”。如果这是长期运行的设备，请根据自己的备份习惯和风险接受程度决定，不要把“自动”两个字直接等同于“更安全”。&lt;/p&gt;&lt;p&gt;刚刷好的系统功能比较基础，但额外软件可以从 LuCI 图形界面安装。点击顶部“系统”，再进入“软件包”。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/21-package-menu.jpg&quot; alt=&quot;ImmortalWrt 系统菜单中的软件包入口&quot; /&gt;&lt;figcaption&gt;ImmortalWrt 系统菜单中的软件包入口&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;进入软件包页面后，先点击“更新列表…”，再在左侧“过滤器”中输入软件名，在结果列表中选择安装。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/22-package-page.jpg&quot; alt=&quot;ImmortalWrt 软件包管理页面&quot; /&gt;&lt;figcaption&gt;ImmortalWrt 软件包管理页面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;这里有一个值得记住的细节：安装和卸载软件包不会直接修改只读系统镜像。系统会在原有镜像上叠加一个 overlay，配置和额外文件通常进入这一层。好处是系统镜像和运行时修改分离，代价是 overlay 空间不是无限的。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第四步：申请预装软件的自定义镜像&lt;a href=&quot;#第四步申请预装软件的自定义镜像&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;直接在系统里安装软件已经够用，但我还想记录一次完整的自定义构建流程，于是回到同一个 Firmware Selector 页面，点击“自定义预安装软件包和/或首次启动脚本”。&lt;/p&gt;&lt;p&gt;页面里已经包含一组基础软件包。我保留这些基础包，只在后面追加自己需要的内容。我的校园网使用深澜认证，所以增加了 &lt;code&gt;bitsrunlogin-go&lt;/code&gt;；为了改善界面，又增加了 Argon 主题：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;bitsrunlogin-go luci-app-bitsrunlogin-go luci-i18n-bitsrunlogin-go-zh-cn&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;luci-theme-argon luci-i18n-argon-config-zh-cn luci-app-argon-config&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;完整的软件包配置如下：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;apk-openssl autocore base-files block-mount bridger ca-bundle default-settings-chn dnsmasq-full dropbear firewall4 fitblk fstools kmod-crypto-hw-safexcel kmod-gpio-button-hotplug kmod-leds-gpio kmod-nf-nathelper kmod-nft-offload libc libgcc libustream-openssl logd luci mtd netifd nftables odhcp6c odhcpd-ipv6only ppp ppp-mod-pppoe procd-ujail uboot-envtools uci uclient-fetch urandom-seed urngd wpad-openssl kmod-usb3 kmod-mt7915e kmod-mt7981-firmware mt7981-wo-firmware automount bitsrunlogin-go luci-app-bitsrunlogin-go luci-i18n-bitsrunlogin-go-zh-cn luci-theme-argon luci-i18n-argon-config-zh-cn luci-app-argon-config&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/23-custom-build-options.jpg&quot; alt=&quot;ImmortalWrt 自定义预安装软件和首次启动脚本页面&quot; /&gt;&lt;figcaption&gt;ImmortalWrt 自定义预安装软件和首次启动脚本页面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;我没有自定义首次启动脚本的需求，所以脚本区域保持为空。配置完成后点击“请求构建”。页面会显示请求位于构建队列中。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/24-build-queued.jpg&quot; alt=&quot;ImmortalWrt 自定义镜像请求位于构建队列中&quot; /&gt;&lt;figcaption&gt;ImmortalWrt 自定义镜像请求位于构建队列中&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;等待构建完成后，页面会出现下载入口。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/25-build-succeeded.jpg&quot; alt=&quot;ImmortalWrt 自定义镜像构建成功&quot; /&gt;&lt;figcaption&gt;ImmortalWrt 自定义镜像构建成功&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;我下载到的文件名类似：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;immortalwrt-25.12.1-17179a901f8d-mediatek-filogic-cudy_tr3000-256mb-v1-squashfs-sysupgrade.bin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;实际文件名应以页面当天生成的结果为准，不要手动猜测或改写中间的构建标识。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第五步：刷入自定义镜像&lt;a href=&quot;#第五步刷入自定义镜像&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;自定义镜像下载完成后，再次进入 ImmortalWrt 的管理页面。系统一般可以通过下面两个地址访问：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;http://192.168.1.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;http://immortalwrt.lan&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;先登录管理界面。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/26-custom-image-login.jpg&quot; alt=&quot;自定义 ImmortalWrt 镜像的登录界面&quot; /&gt;&lt;figcaption&gt;自定义 ImmortalWrt 镜像的登录界面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;进入系统后确认当前仍然是 ImmortalWrt。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/27-custom-image-system-page.jpg&quot; alt=&quot;自定义镜像进入后的系统界面&quot; /&gt;&lt;figcaption&gt;自定义镜像进入后的系统界面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;按照前面相同的路径进入“系统 → 备份与升级”，上传自定义 &lt;code&gt;sysupgrade&lt;/code&gt; 文件。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/28-upload-custom-image.jpg&quot; alt=&quot;上传 ImmortalWrt 自定义 sysupgrade 镜像&quot; /&gt;&lt;figcaption&gt;上传 ImmortalWrt 自定义 sysupgrade 镜像&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;同样取消勾选“保留设置并继续使用当前配置”，点击“继续”，等待路由器完成重启。这样做的目的仍然是避免把上一套镜像的配置直接带进新的软件包集合。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第六步：用 TFTP 恢复 Cudy 官方固件&lt;a href=&quot;#第六步用-tftp-恢复-cudy-官方固件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;TFTP 可以理解成一个非常朴素的局域网文件传输方式：路由器启动时主动向电脑请求一个固定名字的文件。它不像网页上传那样由浏览器发起，而是由路由器的 bootloader 在恢复模式中发起请求。&lt;/p&gt;&lt;p&gt;这次恢复使用 Cudy 官方固件，不是把 ImmortalWrt 通过 TFTP 刷回去。根据 &lt;a href=&quot;https://www.cudy.com/zh-cn/blogs/faq/how-to-recovery-the-cudy-router-from-openwrt-firmware-to-cudy-official-firmware&quot; target=&quot;_blank&quot;&gt;Cudy 官方恢复说明&lt;/a&gt;，我使用 Tftpd64 提供恢复文件。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;准备官方恢复镜像&lt;a href=&quot;#准备官方恢复镜像&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;从 &lt;a href=&quot;https://www.cudy.com/zh-cn/pages/download-center/tr3000-1-0&quot; target=&quot;_blank&quot;&gt;Cudy TR3000 官方下载页面&lt;/a&gt;下载官方固件。本文操作时看到的最新版本是 &lt;code&gt;2.5.21&lt;/code&gt;，文件名为：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;m_upgrade_TR3000_256MB_Flash-R103-2.5.21-20260708-181324-sysupgrade_81571.zip&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;解压后找到 &lt;code&gt;.bin&lt;/code&gt; 文件，将它重命名为：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;recovery.bin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我把它放在：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;E:\Documents\cudy_tr3000-256mb_v1\official&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;实际路径可以不同，但后面的 Tftpd64 根目录必须指向这个文件所在的目录。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;配置电脑网卡和防火墙&lt;a href=&quot;#配置电脑网卡和防火墙&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;用网线连接路由器 LAN 口和电脑。在 Windows 网卡 IPv4 设置中手动填写：&lt;/p&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;项目&lt;/th&gt;&lt;th&gt;值&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;IP 地址&lt;/td&gt;&lt;td&gt;&lt;code&gt;192.168.1.88&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;子网掩码&lt;/td&gt;&lt;td&gt;&lt;code&gt;255.255.255.0&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;默认网关&lt;/td&gt;&lt;td&gt;留空&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DNS&lt;/td&gt;&lt;td&gt;留空&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/29-tftp-static-ip.jpg&quot; alt=&quot;Windows 网卡 IPv4 静态地址配置为 192.168.1.88&quot; /&gt;&lt;figcaption&gt;Windows 网卡 IPv4 静态地址配置为 192.168.1.88&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;为了让 TFTP 请求不被 Windows 拦截，我临时关闭了专用网络和公用网络防火墙。这里只建议在恢复期间短暂关闭，恢复完成后立即重新开启。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/30-disable-firewall.jpg&quot; alt=&quot;Windows 安全中心中临时关闭专用网络防火墙&quot; /&gt;&lt;figcaption&gt;Windows 安全中心中临时关闭专用网络防火墙&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;配置 Tftpd64&lt;a href=&quot;#配置-tftpd64&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Tftpd64 可以从 &lt;a href=&quot;https://github.com/PJO2/tftpd64/releases&quot; target=&quot;_blank&quot;&gt;GitHub Releases&lt;/a&gt; 下载。如果使用 Scoop，也可以安装 extras 仓库里的 &lt;code&gt;tftpd&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;scoop install tftpd&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;打开 Tftpd64 后，设置正确的 Current Directory，也就是包含 &lt;code&gt;recovery.bin&lt;/code&gt; 的目录，再选择电脑当前使用的 &lt;code&gt;192.168.1.88&lt;/code&gt; 网卡。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/31-tftpd-settings.jpg&quot; alt=&quot;Tftpd64 的根目录和网卡配置界面&quot; /&gt;&lt;figcaption&gt;Tftpd64 的根目录和网卡配置界面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;点击“Show Dir”，确认目录列表中确实出现 &lt;code&gt;recovery.bin&lt;/code&gt;。如果这里看不到文件，先修正目录，不要继续操作路由器。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/32-tftpd-show-directory.jpg&quot; alt=&quot;Tftpd64 Show Dir 显示 recovery.bin&quot; /&gt;&lt;figcaption&gt;Tftpd64 Show Dir 显示 recovery.bin&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;触发官方恢复&lt;a href=&quot;#触发官方恢复&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;关闭路由器电源。&lt;/li&gt;
&lt;li&gt;按住路由器侧面的 RESET 键不松手。&lt;/li&gt;
&lt;li&gt;插入 USB-C 电源。&lt;/li&gt;
&lt;li&gt;等待 Tftpd64 界面出现文件传输。&lt;/li&gt;
&lt;li&gt;确认开始传输后再松开 RESET 键。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/33-tftpd-transfer.jpg&quot; alt=&quot;Tftpd64 正在向路由器传输 recovery.bin&quot; /&gt;&lt;figcaption&gt;Tftpd64 正在向路由器传输 recovery.bin&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;TFTP 传输完成不代表恢复已经完成。bootloader 还需要校验镜像，并把内容写入 Flash。传输结束后不要断电，等待路由器自动重启。我这次观察到通常在一分钟以内完成重启。&lt;/p&gt;&lt;p&gt;恢复完成后，指示灯常亮红色。把电脑网卡 IPv4 改回自动获取，并重新开启 Windows 专用网络和公用网络防火墙，然后访问：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;http://192.168.10.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/cudy-tr3000/34-official-firmware-page.jpg&quot; alt=&quot;恢复完成后进入 Cudy 官方固件页面，版本为 2.5.21&quot; /&gt;&lt;figcaption&gt;恢复完成后进入 Cudy 官方固件页面，版本为 2.5.21&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;页面底部显示固件版本已经回到 &lt;code&gt;2.5.21&lt;/code&gt;，这就是我判断官方固件恢复成功的依据。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我最后留下的检查点&lt;a href=&quot;#我最后留下的检查点&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这次流程没有命令行，也没有改动 bootloader。我的检查点只有几个：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;标签明确写着 &lt;code&gt;256 MB 闪存版&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;原厂系统地址是 &lt;code&gt;192.168.10.1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;中间固件启动后地址变成 &lt;code&gt;192.168.1.1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;中间固件使用 &lt;code&gt;cudy_tr3000-256mb-v1-sysupgrade.bin&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;ImmortalWrt Selector 使用 &lt;code&gt;cudy_tr3000-256mb-v1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;目标镜像选择 &lt;code&gt;SYSUPGRADE&lt;/code&gt;，不是 &lt;code&gt;KERNEL&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;刷写 ImmortalWrt 时取消保留旧配置。&lt;/li&gt;
&lt;li&gt;TFTP 恢复使用官方 R103 固件，并重命名为 &lt;code&gt;recovery.bin&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;TFTP 传输完成后继续等待，不能立即断电。&lt;/li&gt;
&lt;li&gt;恢复成功后，官方固件页面显示版本 &lt;code&gt;2.5.21&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;从 2025 年 9 月的第一台，到 2026 年 8 月的第二台，我对这个小路由器的结论没有变：硬件本身并不神秘，真正容易出错的是文件分支和等待时间。选错一次文件，或者在传输完成后过早拔电，都会把一个图形化流程变成维修流程。前者是判断题，后者是耐心题，路由器对此一视同仁。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cudy.com/zh-CN/blogs/faq/openwrt-software-download&quot; target=&quot;_blank&quot;&gt;Cudy 官方 OpenWrt 软件和中间固件下载说明&lt;/a&gt;，256 MB 中间固件下载入口。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cudy.com/zh-cn/pages/download-center/tr3000-1-0&quot; target=&quot;_blank&quot;&gt;Cudy TR3000 官方下载页面&lt;/a&gt;，官方固件和恢复镜像。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cudy.com/zh-cn/blogs/faq/how-to-recovery-the-cudy-router-from-openwrt-firmware-to-cudy-official-firmware&quot; target=&quot;_blank&quot;&gt;Cudy 官方从 OpenWrt 恢复官方固件说明&lt;/a&gt;，TFTP 恢复流程。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://firmware-selector.immortalwrt.org/?version=25.12.1&amp;amp;target=mediatek%2Ffilogic&amp;amp;id=cudy_tr3000-256mb-v1&quot; target=&quot;_blank&quot;&gt;ImmortalWrt Firmware Selector&lt;/a&gt;，选择 &lt;code&gt;cudy_tr3000-256mb-v1&lt;/code&gt; 和 &lt;code&gt;SYSUPGRADE&lt;/code&gt; 镜像。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/PJO2/tftpd64/releases&quot; target=&quot;_blank&quot;&gt;Tftpd64 Releases&lt;/a&gt;，Windows TFTP 服务端下载地址。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://openwrt.org/toh/cudy/tr3000&quot; target=&quot;_blank&quot;&gt;OpenWrt Wiki：Cudy TR3000&lt;/a&gt;，设备支持背景和原厂恢复风险说明。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;</content:encoded></item><item><title>从暂停到理解：GDB 调试完整实践</title><link>https://bfmhno3.github.io/posts/gdb-debugging-guide/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/gdb-debugging-guide/</guid><description>我用一个带有逻辑错误、递归、线程和崩溃路径的 C++20 小程序，从第一次启动 GDB 开始，实作源码定位、断点、栈帧、内存、寄存器、异常、线程、远程调试、优化、脚本和反向执行。</description><pubDate>Tue, 25 Aug 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我一直觉得 GDB 很重要，但以前真正遇到崩溃时，第一反应还是加几行 &lt;code&gt;std::cout&lt;/code&gt;。这多少有点像给发动机贴便利贴来寻找异响，所以我决定做一个小实验：写一个可以编译、可以运行、故意有两个错误的 C++ 程序，然后只用 GDB 观察它如何执行、在哪里偏离预期，以及它为什么最终崩溃。&lt;/p&gt;
&lt;p&gt;这篇文章不是命令字典。我会从一个实际的 artifact 出发，边运行边解释 GDB 如何暂停、观察、改变并继续一个进程。我的环境是 GNU GDB 17.2 和 GCC 15.3.0；命令在 GDB 13/14 上大概率也成立，但线程、TUI、系统调用捕获和反向执行都可能受到平台限制。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先造一个可以被调试的程序&lt;a href=&quot;#先造一个可以被调试的程序&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我把下面的文件保存为 &lt;code&gt;gdb_demo.cpp&lt;/code&gt;。它有四个刻意保留的观察点：动态 &lt;code&gt;vector&lt;/code&gt;、递归求和、两个受互斥锁保护的线程，以及通过 &lt;code&gt;--crash&lt;/code&gt; 参数触发的空指针解引用。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;mutex&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;thread&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;vector&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;recursive_sum&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;, std::&lt;/span&gt;&lt;/span&gt;&lt;span&gt;size_t&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;recursive_sum&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;worker&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt;, std::&lt;/span&gt;&lt;/span&gt;&lt;span&gt;mutex&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mutex&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;lock_guard&lt;/span&gt;&lt;span&gt;&amp;lt;std::&lt;/span&gt;&lt;span&gt;mutex&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;lock&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mutex&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;argc&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;char*&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;argv&lt;/span&gt;&lt;span&gt;[]) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;crash&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;argc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;argv&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;--crash&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;9&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;recursive_sum&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;average&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;static_cast&lt;/span&gt;&lt;span&gt;&amp;lt;double&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;total=&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;, average=&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;average&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;, expected_average=6&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;mutex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mutex&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;thread&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;worker&lt;/span&gt;&lt;span&gt;, std::&lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt;), std::&lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mutex&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;thread&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;second&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;worker&lt;/span&gt;&lt;span&gt;, std::&lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt;), std::&lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mutex&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;second&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;counter=&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;crash&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;int*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pointer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nullptr&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;about to dereference a null pointer&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;pointer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;我先用可调试构建编译它：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;g++&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-std=c++20&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-g3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-O0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-fno-omit-frame-pointer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-Wall&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-Wextra&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gdb_demo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gdb_demo.cpp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-g3&lt;/code&gt; 把源码、变量和宏等调试信息写进可执行文件；&lt;code&gt;-O0&lt;/code&gt; 关闭优化，让源码行和实际执行顺序尽量保持直觉；&lt;code&gt;-fno-omit-frame-pointer&lt;/code&gt; 保留帧指针，便于调用栈和低层检查。这里的 &lt;code&gt;-Wall -Wextra&lt;/code&gt; 是编译器警告，不是 GDB 功能，但我希望在程序进入调试器前先消灭显眼的问题。&lt;/p&gt;&lt;p&gt;正常运行得到：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;total=24, average=8, expected_average=6&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;counter=2000&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;3 + 5 + 7 + 9 = 24&lt;/code&gt;，两个线程各自增加 &lt;code&gt;1000&lt;/code&gt; 次，所以 &lt;code&gt;counter=2000&lt;/code&gt; 是稳定结果。平均值却是 &lt;code&gt;8&lt;/code&gt;，而我写在输出里的期望值是 &lt;code&gt;6&lt;/code&gt;。错误就在 &lt;code&gt;values.size() - 1&lt;/code&gt;，应该除以 &lt;code&gt;values.size()&lt;/code&gt;。这是一种 GDB 很擅长处理的问题：程序没有崩溃，编译器也没有理由报警，但状态已经错了。&lt;/p&gt;&lt;p&gt;加上参数后，程序会进入另一条路径：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;about to dereference a null pointer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Segmentation fault&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第一次完整调试：暂停，观察，继续&lt;a href=&quot;#第一次完整调试暂停观察继续&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;启动 GDB 的命令属于 Shell，不属于 GDB：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;gdb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./gdb_demo&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;看到 &lt;code&gt;(gdb)&lt;/code&gt; 之后，才进入 GDB 命令语言。程序自己的标准输入则是第三种东西，例如程序调用 &lt;code&gt;std::cin&lt;/code&gt; 时，输入才会被程序读取，而不会自动变成 GDB 命令。这个边界一开始很容易混淆，我也混淆过（把 &lt;code&gt;run&lt;/code&gt; 输入成程序参数，结果得到了一次相当安静的失败）。&lt;/p&gt;&lt;p&gt;我先走一遍最短路径：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) break main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) next&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) print values&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) backtrace&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) quit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;break main&lt;/code&gt; 在 &lt;code&gt;main&lt;/code&gt; 入口设置断点，&lt;code&gt;run&lt;/code&gt; 启动程序并停在那里，&lt;code&gt;next&lt;/code&gt; 执行当前源码行但跳过被调用函数，&lt;code&gt;step&lt;/code&gt; 则进入函数。&lt;code&gt;print&lt;/code&gt; 读取当前上下文中的表达式，&lt;code&gt;continue&lt;/code&gt; 继续运行到下一个断点或信号，&lt;code&gt;backtrace&lt;/code&gt; 打印调用栈，&lt;code&gt;quit&lt;/code&gt; 退出。&lt;/p&gt;&lt;p&gt;断点像给程序安装一个可控的闸门。进程跑到闸门前暂停，我可以查看货物，也可以决定放行、改变一件货物，甚至把它送回另一条轨道。类比到这里就够了，后面我会直接使用 GDB 的术语。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;符号为什么重要&lt;a href=&quot;#符号为什么重要&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;编译器最终生成的是机器指令，CPU 并不知道 &lt;code&gt;main&lt;/code&gt; 或 &lt;code&gt;average&lt;/code&gt; 这些名字。&lt;code&gt;-g3&lt;/code&gt; 让可执行文件额外携带符号表、源码文件、行号和变量位置，GDB 才能把某个指令地址映射回 &lt;code&gt;gdb_demo.cpp:24&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;没有调试信息时，GDB 仍然可以停在地址、查看寄存器和反汇编，但你会失去大部分源码级语义：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) file ./gdb_demo_without_debug_info&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) break main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;No symbol table is loaded.  Use the &quot;file&quot; command.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;按调试任务使用断点&lt;a href=&quot;#按调试任务使用断点&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;我通常先问：程序应该在哪些位置停下来？&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) break main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) break recursive_sum&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) break gdb_demo.cpp:24&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) break gdb_demo.cpp:24 if values.size() != 4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) rbreak worker.*&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) tbreak gdb_demo.cpp:40&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) info breakpoints&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) disable 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) enable 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) clear gdb_demo.cpp:24&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) delete 1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;break&lt;/code&gt; 可以按函数、文件和行号设置，也可以附加条件。&lt;code&gt;rbreak&lt;/code&gt; 接受正则表达式，&lt;code&gt;tbreak&lt;/code&gt; 只命中一次。每个断点都有编号，&lt;code&gt;info breakpoints&lt;/code&gt; 显示位置、启用状态和命中次数；这个实验中 &lt;code&gt;recursive_sum&lt;/code&gt; 会递归进入 5 个栈帧，但条件断点只在满足条件的那一帧停下。&lt;/p&gt;&lt;p&gt;如果我在共享库加载前设置断点，GDB 可能显示：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Breakpoint 3 (some_library_function) pending.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这不是命中。它表示 GDB 暂时找不到符号，等目标共享库加载后再尝试解析。可以用 &lt;code&gt;set breakpoint pending on&lt;/code&gt; 明确允许这种行为，再用 &lt;code&gt;info sharedlibrary&lt;/code&gt; 检查库是否已加载。&lt;/p&gt;&lt;p&gt;&lt;code&gt;clear&lt;/code&gt; 按源码位置清理，&lt;code&gt;delete&lt;/code&gt; 按断点编号删除，&lt;code&gt;disable&lt;/code&gt; 只暂时关闭。调试一个复杂程序时，我更常用 &lt;code&gt;disable&lt;/code&gt;，因为保留编号和条件比重新设置更少出错。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;控制执行：不要把程序当成黑盒&lt;a href=&quot;#控制执行不要把程序当成黑盒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这些命令的区别值得单独记住：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;run&lt;/code&gt; 从头启动；可以写成 &lt;code&gt;run --crash&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;start&lt;/code&gt; 在 &lt;code&gt;main&lt;/code&gt; 的第一行附近停下，适合快速检查参数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;starti&lt;/code&gt; 从第一条机器指令开始，适合研究启动代码。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;continue&lt;/code&gt; 放行直到下一个断点、信号或退出。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;next&lt;/code&gt; 执行当前源码行，不进入函数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;step&lt;/code&gt; 执行当前源码行，并进入函数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;finish&lt;/code&gt; 运行到当前函数返回，并显示返回值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;until 30&lt;/code&gt; 运行到当前函数中的指定行或更高层位置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;advance gdb_demo.cpp:35&lt;/code&gt; 运行到指定位置，不会倒退控制流。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jump gdb_demo.cpp:30&lt;/code&gt; 直接改变下一条源码位置，可能跳过初始化。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;return&lt;/code&gt; 强制当前函数返回，可附加返回值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;signal SIGINT&lt;/code&gt; 把信号交给被调试程序。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;code&gt;jump&lt;/code&gt; 和 &lt;code&gt;return&lt;/code&gt; 不是修复代码。它们是实验工具，用来回答如果跳过某个分支会发生什么；跳过构造函数、析构函数或锁操作很容易破坏程序状态。我只在已经知道风险的局部现场使用它们。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;栈帧：递归时我到底在看谁&lt;a href=&quot;#栈帧递归时我到底在看谁&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;程序每调用一个函数，通常就产生一个栈帧。递归调用 &lt;code&gt;recursive_sum&lt;/code&gt; 时，4 个元素会产生 4 个非终止调用帧，再加上终止帧和 &lt;code&gt;main&lt;/code&gt;，所以我可以观察到 6 层相关上下文。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) break recursive_sum&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) backtrace&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) frame 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) info frame&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) info args&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) info locals&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) up&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) down&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;backtrace&lt;/code&gt; 或 &lt;code&gt;bt&lt;/code&gt; 从当前帧向调用者打印；&lt;code&gt;frame 2&lt;/code&gt; 直接切换到编号为 &lt;code&gt;2&lt;/code&gt; 的帧；&lt;code&gt;up&lt;/code&gt; 和 &lt;code&gt;down&lt;/code&gt; 在调用者与被调用者之间移动。关键点是：&lt;code&gt;print index&lt;/code&gt; 读取的是当前 frame 的 &lt;code&gt;index&lt;/code&gt;，不是我脑中刚才那个递归层级的 &lt;code&gt;index&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;崩溃路径的命令更加直接：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set args --crash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) bt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) bt full&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) frame 0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) print pointer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;本次崩溃稳定停在 &lt;code&gt;gdb_demo.cpp:40&lt;/code&gt;，&lt;code&gt;pointer&lt;/code&gt; 的值是 &lt;code&gt;0x0&lt;/code&gt;。地址本身依赖运行环境，语义不依赖：当前线程在解引用空指针。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;数据和表达式：让 GDB 替我问问题&lt;a href=&quot;#数据和表达式让-gdb-替我问问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) print total&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) p average&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) display total&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) display /x total&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) undisplay 1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) ptype values&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) whatis values&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set print pretty on&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set print elements 20&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) print values[2]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) print values.data()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) print *values.data()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;print&lt;/code&gt; 可以求 C++ 表达式。&lt;code&gt;display&lt;/code&gt; 会在每次停下时自动打印，适合观察循环变量；&lt;code&gt;ptype&lt;/code&gt; 展开类型定义，&lt;code&gt;whatis&lt;/code&gt; 给出更短的类型名。对 &lt;code&gt;vector&lt;/code&gt;、结构体和嵌套对象，&lt;code&gt;set print pretty on&lt;/code&gt; 通常更容易阅读。&lt;/p&gt;&lt;p&gt;我在第 24 行设置断点后，可以确认 &lt;code&gt;total&lt;/code&gt; 已经是 &lt;code&gt;24&lt;/code&gt;，而 &lt;code&gt;average&lt;/code&gt; 尚未完成初始化。此时打印未初始化的局部变量得到垃圾值不是 GDB 出错，而是程序还没有给它一个有效值。继续到下一行后再打印，才会看到 &lt;code&gt;average=8&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;表达式也可以带类型转换：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) print (long) counter&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) print (double) total / values.size()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;调用函数要谨慎。&lt;code&gt;print some_function()&lt;/code&gt; 可能修改全局状态、推进迭代器、申请内存或拿锁。GDB 正在观察程序时替我调用函数，结果可能比原始执行更复杂，我优先读取变量和内存。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;内存、寄存器和汇编&lt;a href=&quot;#内存寄存器和汇编&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;x/nfu address&lt;/code&gt; 中，&lt;code&gt;n&lt;/code&gt; 是数量，&lt;code&gt;f&lt;/code&gt; 是格式，&lt;code&gt;u&lt;/code&gt; 是单位。例如：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) x/4dw values.data()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) x/16xb values.data()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) x/s pointer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) x/i $pc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) info registers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) disassemble /m recursive_sum&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) disassemble /r main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;x/4dw&lt;/code&gt; 按十进制 word 查看 4 个单元，&lt;code&gt;x/16xb&lt;/code&gt; 按十六进制 byte 查看 16 个字节，&lt;code&gt;x/s&lt;/code&gt; 把地址解释为字符串，&lt;code&gt;x/i $pc&lt;/code&gt; 查看程序计数器当前位置的指令。&lt;code&gt;info registers&lt;/code&gt; 显示寄存器，&lt;code&gt;$pc&lt;/code&gt; 是当前指令地址，&lt;code&gt;$sp&lt;/code&gt; 指向当前栈顶，&lt;code&gt;$fp&lt;/code&gt; 在保留帧指针的构建中帮助定位当前栈帧。&lt;/p&gt;&lt;p&gt;&lt;code&gt;disassemble /m&lt;/code&gt; 尝试把源码行和机器指令混排，&lt;code&gt;disassemble /r&lt;/code&gt; 附带原始机器字节。也可以使用：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) layout asm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这时我看到的不是 C++，而是编译器为 C++ 选择的实际指令。源码的第 24 行可能对应多条指令，单步时也可能在同一行内移动。GDB 是源码和 CPU 状态之间的显微镜，不是源码执行动画播放器。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;改变状态：有时我需要一个反事实&lt;a href=&quot;#改变状态有时我需要一个反事实&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set variable average = 6&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set variable counter = 0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set $expected = 6&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) print $expected&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set $pc = *some_address&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set $rax = 0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;set variable&lt;/code&gt; 修改程序变量，&lt;code&gt;$expected&lt;/code&gt; 是 GDB convenience variable，修改寄存器则直接影响后续机器指令。它们适合验证假设：如果平均值已经是 &lt;code&gt;6&lt;/code&gt;，后续分支是否还会失败？某个寄存器清零后，崩溃位置是否变化？&lt;/p&gt;&lt;p&gt;这不是源代码修复。类型不匹配、越界写入、跳过锁和破坏对象不变量，都可能制造新的假象。实验结束后我会重新启动进程，而不是把被 GDB 改过的状态当成测试结果。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;watchpoint：捕获变化，而不是猜行号&lt;a href=&quot;#watchpoint捕获变化而不是猜行号&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) break worker&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) watch counter&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) rwatch counter&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) awatch counter&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) info watchpoints&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;watch&lt;/code&gt; 在表达式写入后停下，&lt;code&gt;rwatch&lt;/code&gt; 在读取后停下，&lt;code&gt;awatch&lt;/code&gt; 在读取或写入后停下。对简单标量，GDB 大概率使用硬件 watchpoint；硬件寄存器数量有限，复杂表达式和大范围内存可能无法使用。&lt;/p&gt;&lt;p&gt;线程版本中 &lt;code&gt;counter&lt;/code&gt; 最终从 &lt;code&gt;0&lt;/code&gt; 变化到 &lt;code&gt;2000&lt;/code&gt;。因为我用 &lt;code&gt;std::mutex&lt;/code&gt; 保护了自增，结果稳定；普通断点只能说明某个线程停过，不能证明不存在数据竞争。要调查竞争，我会结合 &lt;code&gt;info threads&lt;/code&gt;、&lt;code&gt;thread apply all bt&lt;/code&gt;，再使用 ThreadSanitizer 等专门工具，而不是把 GDB 当成竞态检测器。&lt;/p&gt;&lt;p&gt;异常和系统事件也可以捕获，但能力依赖平台：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) catch throw&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) catch signal SIGSEGV&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) catch syscall write&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) handle SIGPIPE nostop noprint pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;catch throw&lt;/code&gt; 对 C++ 异常很有用；&lt;code&gt;catch syscall&lt;/code&gt; 只在目标架构和 GDB 支持的系统调用接口上可用。&lt;code&gt;handle&lt;/code&gt; 控制信号到达时 GDB 是否暂停、打印以及是否传递给程序。不要把某个平台上的 catchpoint 行为写成所有 Linux、BSD 和嵌入式目标都相同。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参数、环境、线程与崩溃文件&lt;a href=&quot;#参数环境线程与崩溃文件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;从命令行复现问题时，我会把参数显式放进调试会话：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set args --crash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) show args&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set environment MODE debug&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) show environment MODE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;也可以在 Shell 中使用重定向：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;input&quot;&lt;/span&gt;&lt;span&gt; &amp;gt; &lt;/span&gt;&lt;span&gt;input.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;gdb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--args&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./gdb_demo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--crash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;gdb&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &amp;lt; &lt;/span&gt;&lt;span&gt;input.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;线程调试的最小工作流如下：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) info threads&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) thread 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) bt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) thread apply all bt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) break worker&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;断点可能停在任意命中断点的线程。先看 &lt;code&gt;info threads&lt;/code&gt;，再用 &lt;code&gt;thread&lt;/code&gt; 切换，最后用 &lt;code&gt;frame&lt;/code&gt;、&lt;code&gt;info locals&lt;/code&gt; 检查那个线程的上下文。线程 ID 是运行时生成的，不应写进教程的固定结论。&lt;/p&gt;&lt;p&gt;如果程序产生 core 文件，可以脱离原始运行现场：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;gdb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./gdb_demo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;core&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;或者在 GDB 中：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) core-file core&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) bt full&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) info registers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) x/16gx $sp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;可执行文件、core 和调试符号必须匹配。换过二进制后继续解释旧 core，得到的行号和变量很可能完全不可信。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;子进程、共享库和远程目标&lt;a href=&quot;#子进程共享库和远程目标&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;调试共享库先确认加载状态：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) info sharedlibrary&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set breakpoint pending on&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) break library_function&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;程序 &lt;code&gt;fork&lt;/code&gt; 后，GDB 默认行为可以调整：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set follow-fork-mode child&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) set detach-on-fork off&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) info inferiors&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) inferior 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;follow-fork-mode&lt;/code&gt; 决定跟随父进程还是子进程，&lt;code&gt;detach-on-fork off&lt;/code&gt; 保留两个 inferior，代价是调试状态更复杂。&lt;/p&gt;&lt;p&gt;远程调试需要目标端有 &lt;code&gt;gdbserver&lt;/code&gt;、网络连通和权限允许。最小流程是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 目标机&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;gdbserver&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:1234&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./gdb_demo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--crash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 主机&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;gdb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./gdb_demo&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;gdb&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;remote&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TARGET_HOST:1234&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;gdb&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;远程 GDB 不是把本地文件复制过去；主机上的符号和目标机上的实际可执行文件、库、架构必须对应。端口、防火墙、路径映射和权限都是前置条件，我不会把这条命令伪装成离线环境中的必然成功路径。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;优化构建为什么会和源码吵架&lt;a href=&quot;#优化构建为什么会和源码吵架&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;确认逻辑后，我再编译优化版本：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;g++&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-std=c++20&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-g3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-O2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-fno-omit-frame-pointer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-Wall&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-Wextra&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gdb_demo_O2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gdb_demo.cpp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-O2&lt;/code&gt; 可能内联 &lt;code&gt;recursive_sum&lt;/code&gt;、合并变量、改变生命周期和重排指令。于是 GDB 可能显示：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) print some_local&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$1 = &amp;lt;optimized out&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;也可能出现源码行跳跃、多个变量共享一个寄存器，或者断点虽然设置成功却没有在我预期的行停下。这不是调试器随机失忆，而是编译器已经改变了程序的机器级形状。&lt;/p&gt;&lt;p&gt;我的流程是：先用 &lt;code&gt;-O0 -g3&lt;/code&gt; 复现和定位，再用 &lt;code&gt;-O2 -g3&lt;/code&gt; 确认优化环境中的行为。如果只在优化版本崩溃，优先保存 core、检查汇编和寄存器，不要为了让 GDB 显示漂亮变量而误称 &lt;code&gt;-O0&lt;/code&gt; 的结果代表生产环境。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;TUI、命令脚本和一点自动化&lt;a href=&quot;#tui命令脚本和一点自动化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;终端界面可以把源码、汇编和寄存器放在同一屏：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) layout src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) layout split&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) layout regs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) focus cmd&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) refresh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;窗口布局只改变显示，不改变被调试程序。退出 TUI 可以按 &lt;code&gt;Ctrl-x Ctrl-a&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;重复操作适合放进脚本，例如 &lt;code&gt;debug.gdb&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set pagination off&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set print pretty on&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;break main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;commands&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;silent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;printf &quot;stopped in main, argc=%d\n&quot;, argc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;info locals&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;执行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) source debug.gdb&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我也可以在 &lt;code&gt;~/.gdbinit&lt;/code&gt; 中放通用设置，或定义一个小命令：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def btfull&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;set pagination off&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;thread apply all bt full&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;GDB 支持 Python API，可以访问 inferior、断点和事件对象。但我把它留在自动化边界内：先用 &lt;code&gt;source&lt;/code&gt; 和 &lt;code&gt;define&lt;/code&gt; 解决 80% 的重复操作，只有当重复逻辑确实需要状态管理时才写 Python。调试器已经够复杂了，没必要为了打印一行栈信息再造一个框架。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;记录与反向执行&lt;a href=&quot;#记录与反向执行&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在支持的本地目标上，我可以尝试：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) record full&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) reverse-step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) reverse-continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;(gdb) record stop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它通过记录执行状态，让我在某个错误发生后反向回到之前的指令。代价是额外的时间、内存和记录存储；系统调用、设备访问、线程和某些架构并不都能被完整重放。它不是所有程序都适用的时间机器，尤其不是网络服务的万能撤销按钮。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一张按任务分组的功能地图&lt;a href=&quot;#一张按任务分组的功能地图&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;























































































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;常用命令&lt;/th&gt;&lt;th&gt;最短用途&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;运行控制&lt;/td&gt;&lt;td&gt;&lt;code&gt;run&lt;/code&gt;, &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;starti&lt;/code&gt;&lt;/td&gt;&lt;td&gt;启动并选择初始停点&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;继续执行&lt;/td&gt;&lt;td&gt;&lt;code&gt;continue&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt;, &lt;code&gt;step&lt;/code&gt;&lt;/td&gt;&lt;td&gt;放行、越过或进入函数&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;改变流程&lt;/td&gt;&lt;td&gt;&lt;code&gt;finish&lt;/code&gt;, &lt;code&gt;until&lt;/code&gt;, &lt;code&gt;advance&lt;/code&gt;&lt;/td&gt;&lt;td&gt;运行到返回或指定位置&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;实验跳转&lt;/td&gt;&lt;td&gt;&lt;code&gt;jump&lt;/code&gt;, &lt;code&gt;return&lt;/code&gt;, &lt;code&gt;signal&lt;/code&gt;&lt;/td&gt;&lt;td&gt;改变下一步、强制返回、发送信号&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;断点&lt;/td&gt;&lt;td&gt;&lt;code&gt;break&lt;/code&gt;, &lt;code&gt;rbreak&lt;/code&gt;, &lt;code&gt;tbreak&lt;/code&gt;&lt;/td&gt;&lt;td&gt;按函数、正则或位置暂停&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;断点管理&lt;/td&gt;&lt;td&gt;&lt;code&gt;info breakpoints&lt;/code&gt;, &lt;code&gt;clear&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;disable&lt;/code&gt;, &lt;code&gt;enable&lt;/code&gt;&lt;/td&gt;&lt;td&gt;查看和管理断点&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;数据&lt;/td&gt;&lt;td&gt;&lt;code&gt;print&lt;/code&gt;, &lt;code&gt;display&lt;/code&gt;, &lt;code&gt;ptype&lt;/code&gt;, &lt;code&gt;whatis&lt;/code&gt;&lt;/td&gt;&lt;td&gt;求表达式、自动显示、查看类型&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;栈&lt;/td&gt;&lt;td&gt;&lt;code&gt;bt&lt;/code&gt;, &lt;code&gt;frame&lt;/code&gt;, &lt;code&gt;up&lt;/code&gt;, &lt;code&gt;down&lt;/code&gt;, &lt;code&gt;info frame&lt;/code&gt;&lt;/td&gt;&lt;td&gt;查看和切换调用上下文&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;局部状态&lt;/td&gt;&lt;td&gt;&lt;code&gt;info locals&lt;/code&gt;, &lt;code&gt;info args&lt;/code&gt;&lt;/td&gt;&lt;td&gt;查看当前帧变量和参数&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;内存&lt;/td&gt;&lt;td&gt;&lt;code&gt;x&lt;/code&gt;, &lt;code&gt;find&lt;/code&gt;&lt;/td&gt;&lt;td&gt;按格式查看或搜索内存&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CPU&lt;/td&gt;&lt;td&gt;&lt;code&gt;info registers&lt;/code&gt;, &lt;code&gt;disassemble&lt;/code&gt;&lt;/td&gt;&lt;td&gt;检查寄存器和机器指令&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;改状态&lt;/td&gt;&lt;td&gt;&lt;code&gt;set variable&lt;/code&gt;, &lt;code&gt;set $name&lt;/code&gt;&lt;/td&gt;&lt;td&gt;修改变量或 GDB 便利变量&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;变化捕获&lt;/td&gt;&lt;td&gt;&lt;code&gt;watch&lt;/code&gt;, &lt;code&gt;rwatch&lt;/code&gt;, &lt;code&gt;awatch&lt;/code&gt;&lt;/td&gt;&lt;td&gt;在读写发生时暂停&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;异常信号&lt;/td&gt;&lt;td&gt;&lt;code&gt;catch throw&lt;/code&gt;, &lt;code&gt;catch signal&lt;/code&gt;, &lt;code&gt;handle&lt;/code&gt;&lt;/td&gt;&lt;td&gt;捕获异常和信号&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;系统调用&lt;/td&gt;&lt;td&gt;&lt;code&gt;catch syscall&lt;/code&gt;&lt;/td&gt;&lt;td&gt;在支持的平台捕获系统调用&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;线程&lt;/td&gt;&lt;td&gt;&lt;code&gt;info threads&lt;/code&gt;, &lt;code&gt;thread&lt;/code&gt;, &lt;code&gt;thread apply all bt&lt;/code&gt;&lt;/td&gt;&lt;td&gt;列出、切换和批量查看线程&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;进程&lt;/td&gt;&lt;td&gt;&lt;code&gt;set follow-fork-mode&lt;/code&gt;, &lt;code&gt;set detach-on-fork&lt;/code&gt;, &lt;code&gt;info inferiors&lt;/code&gt;&lt;/td&gt;&lt;td&gt;控制 fork 后的跟随对象&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;共享库&lt;/td&gt;&lt;td&gt;&lt;code&gt;info sharedlibrary&lt;/code&gt;, &lt;code&gt;set breakpoint pending on&lt;/code&gt;&lt;/td&gt;&lt;td&gt;检查库和延迟断点&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;远程&lt;/td&gt;&lt;td&gt;&lt;code&gt;target remote&lt;/code&gt;&lt;/td&gt;&lt;td&gt;连接 &lt;code&gt;gdbserver&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;TUI&lt;/td&gt;&lt;td&gt;&lt;code&gt;layout src&lt;/code&gt;, &lt;code&gt;layout split&lt;/code&gt;, &lt;code&gt;layout regs&lt;/code&gt;, &lt;code&gt;focus&lt;/code&gt;&lt;/td&gt;&lt;td&gt;查看源码、汇编和寄存器&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;自动化&lt;/td&gt;&lt;td&gt;&lt;code&gt;source&lt;/code&gt;, &lt;code&gt;define&lt;/code&gt;, &lt;code&gt;commands&lt;/code&gt;&lt;/td&gt;&lt;td&gt;加载脚本和定义命令&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;反向&lt;/td&gt;&lt;td&gt;&lt;code&gt;record full&lt;/code&gt;, &lt;code&gt;reverse-step&lt;/code&gt;, &lt;code&gt;reverse-continue&lt;/code&gt;&lt;/td&gt;&lt;td&gt;在记录数据中回退执行&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这张表把日常源码调试能力串起来，但不把架构专用命令、IDE 前端、Valgrind 或 AddressSanitizer 冒充成 GDB 功能。它们可以协作，边界仍然存在。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见故障排查&lt;a href=&quot;#常见故障排查&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;












































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;症状&lt;/th&gt;&lt;th&gt;大概率原因&lt;/th&gt;&lt;th&gt;先执行什么&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;No symbol table is loaded&lt;/code&gt;&lt;/td&gt;&lt;td&gt;没有 &lt;code&gt;-g&lt;/code&gt;，或加载了错误文件&lt;/td&gt;&lt;td&gt;&lt;code&gt;file ./gdb_demo&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Breakpoint pending&lt;/code&gt;&lt;/td&gt;&lt;td&gt;共享库尚未加载或符号名不匹配&lt;/td&gt;&lt;td&gt;&lt;code&gt;info sharedlibrary&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Cannot access memory&lt;/code&gt;&lt;/td&gt;&lt;td&gt;地址无效、对象已释放或当前 frame 不对&lt;/td&gt;&lt;td&gt;&lt;code&gt;info registers&lt;/code&gt;、&lt;code&gt;frame 0&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;optimized out&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;优化消除了变量或改变了生命周期&lt;/td&gt;&lt;td&gt;用 &lt;code&gt;-O0 -g3&lt;/code&gt; 重建&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;程序直接退出&lt;/td&gt;&lt;td&gt;没有断点，或断点位置不在实际路径&lt;/td&gt;&lt;td&gt;&lt;code&gt;start&lt;/code&gt;、&lt;code&gt;break main&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;断点不命中&lt;/td&gt;&lt;td&gt;行没有生成可执行代码、条件不成立或库未加载&lt;/td&gt;&lt;td&gt;&lt;code&gt;info breakpoints&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;print&lt;/code&gt; 看起来不对&lt;/td&gt;&lt;td&gt;当前停在错误的栈帧或变量尚未初始化&lt;/td&gt;&lt;td&gt;&lt;code&gt;bt&lt;/code&gt;、&lt;code&gt;frame&lt;/code&gt;、&lt;code&gt;info locals&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;我把“断点不命中”分成状态问题而不是玄学问题。先看断点编号、启用状态和命中次数，再确认控制流是否真的经过那里；不要一上来就重新编译十次，然后把所有的不确定性归咎于 GDB（虽然这样做有一种很强的仪式感）。&lt;/p&gt;&lt;p&gt;我把第 24 行改成下面这样，再重新编译，而不是在 GDB 里永久修改变量：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;average&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;static_cast&lt;/span&gt;&lt;span&gt;&amp;lt;double&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;修正后的稳定输出是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;total=24, average=6, expected_average=6&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;counter=2000&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这两个版本的线程计数相同，只有平均值从 &lt;code&gt;8&lt;/code&gt; 变成 &lt;code&gt;6&lt;/code&gt;。这正是我想要的调试闭环：GDB 帮我定位原因，源代码和编译器才负责产生修复后的程序。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我以后会怎样使用 GDB&lt;a href=&quot;#我以后会怎样使用-gdb&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这次实验里，错误平均值从输出中的 &lt;code&gt;8&lt;/code&gt; 追到了第 24 行，线程计数从 &lt;code&gt;0&lt;/code&gt; 走到了稳定的 &lt;code&gt;2000&lt;/code&gt;，崩溃则被定位到空指针解引用的第 40 行。真正改变我习惯的不是记住了多少命令，而是把调试变成一个循环：提出一个关于状态的猜测，设置最窄的停点，观察证据，再决定是否继续。&lt;/p&gt;&lt;p&gt;以后我会先保留一个带 &lt;code&gt;-g3 -O0&lt;/code&gt; 的可调试构建，把 &lt;code&gt;gdb ./program&lt;/code&gt; 当成和 &lt;code&gt;./program&lt;/code&gt; 一样自然的动作。等程序在优化、线程或远程机器上表现不同，我再逐层打开 &lt;code&gt;info threads&lt;/code&gt;、寄存器、汇编和记录功能，而不是继续往代码里撒 &lt;code&gt;printf&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;再往前看，调试器大概不会消失。编译器会更激进，异步任务会更多，机器学习生成的代码也会让源码与机器指令之间的距离继续变长。能暂停一个进程只是起点，真正有价值的是保留一条从现象到状态、从状态到原因的证据链。好的调试工具会缩短这条链；好的调试习惯则让我在按下 &lt;code&gt;run&lt;/code&gt; 之前，已经知道自己准备验证什么。&lt;/p&gt;&lt;p&gt;现在我可以删掉这个实验里的错误，重新编译，然后去吃点东西。GDB 不会替我修复 &lt;code&gt;values.size() - 1&lt;/code&gt;，它只是非常耐心地证明我确实写错了。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>LaTeX 项目结构与 latexmk 配置</title><link>https://bfmhno3.github.io/posts/latex-project-structure/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/latex-project-structure/</guid><description>我用 LaTeX 写东西时的项目目录结构、main.tex 模板，以及一份跨平台的 .latexmkrc 配置。</description><pubDate>Thu, 18 Jun 2026 03:20:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;目录结构&lt;a href=&quot;#目录结构&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;C:.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.gitignore&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.latexmkrc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;LICENSE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;main.tex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;refs.bib&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├─contents&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├─docs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├─figs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└─settings&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;format.tex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;packages.tex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;main.tex&lt;/code&gt;：入口文件，只负责把各部分拼起来。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;refs.bib&lt;/code&gt;：参考文献数据库。用 BibTeX 格式存储文献条目，正文里用 &lt;code&gt;\cite{key}&lt;/code&gt; 引用。配合 &lt;code&gt;biblatex&lt;/code&gt; 使用时，在 &lt;code&gt;format.tex&lt;/code&gt; 里用 &lt;code&gt;\addbibresource{refs.bib}&lt;/code&gt; 指定路径。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;contents/&lt;/code&gt;：章节内容，按章（&lt;code&gt;book&lt;/code&gt;/&lt;code&gt;report&lt;/code&gt; 类）或按节（&lt;code&gt;article&lt;/code&gt; 类）建子文件夹。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;figs/&lt;/code&gt;：图片资源。配合 &lt;code&gt;\graphicspath{{figs/}}&lt;/code&gt; 使用，正文里直接写文件名就行。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docs/&lt;/code&gt;：项目文档，比如编译好的 PDF、使用说明等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;settings/packages.tex&lt;/code&gt;：宏包导入。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;settings/format.tex&lt;/code&gt;：格式配置（页面布局、标题样式、字体等）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.latexmkrc&lt;/code&gt;：latexmk 的编译配置，后面会详细讲。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.gitignore&lt;/code&gt;：LaTeX 项目的忽略模板可以参考 &lt;a href=&quot;https://github.com/github/gitignore/blob/main/TeX.gitignore&quot; target=&quot;_blank&quot;&gt;github/gitignore/TeX.gitignore&lt;/a&gt;。注意官方模板里没有 &lt;code&gt;build/&lt;/code&gt; 目录，如果你像我一样把编译产物输出到 &lt;code&gt;build/&lt;/code&gt;，记得手动加上。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;code&gt;packages.tex&lt;/code&gt; 和 &lt;code&gt;format.tex&lt;/code&gt; 的最小示例：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% settings/packages.tex -- 只管导入，不管配置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{fontspec}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{xeCJK}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{geometry}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{fancyhdr}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{amsmath}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{amssymb}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{amsfonts}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{mathtools}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{graphicx}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{tabularray}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{subcaption}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{listings}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{enumitem}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{siunitx}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;[backend=biber, style=gb7714-2015]{biblatex}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;[colorlinks=true, linkcolor=blue, citecolor=blue, urlcolor=blue]{hyperref}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;[capitalise]{cleveref}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% settings/format.tex -- 字体、布局、宏包的后续配置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setmainfont&lt;/span&gt;&lt;span&gt;{TeX Gyre Termes}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setsansfont&lt;/span&gt;&lt;span&gt;{TeX Gyre Heros}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setmonofont&lt;/span&gt;&lt;span&gt;{TeX Gyre Cursor}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setCJKmainfont&lt;/span&gt;&lt;span&gt;{Source Han Serif SC}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setCJKsansfont&lt;/span&gt;&lt;span&gt;{Source Han Sans SC}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setCJKmonofont&lt;/span&gt;&lt;span&gt;{Source Han Mono SC}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\geometry&lt;/span&gt;&lt;span&gt;{a4paper, margin=2.5cm}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\pagestyle&lt;/span&gt;&lt;span&gt;{fancy}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\fancyhf&lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\fancyhead&lt;/span&gt;&lt;span&gt;[L]{&lt;/span&gt;&lt;span&gt;\leftmark&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\fancyfoot&lt;/span&gt;&lt;span&gt;[C]{&lt;/span&gt;&lt;span&gt;\thepage&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\renewcommand&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;\headrulewidth&lt;/span&gt;&lt;span&gt;}{0.4pt}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\UseTblrLibrary&lt;/span&gt;&lt;span&gt;{booktabs, siunitx}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\graphicspath&lt;/span&gt;&lt;span&gt;{{figs/}}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\sisetup&lt;/span&gt;&lt;span&gt;{range-phrase=&lt;/span&gt;&lt;span&gt;\ensuremath&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;\sim&lt;/span&gt;&lt;span&gt;}, range-units=single}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\addbibresource&lt;/span&gt;&lt;span&gt;{refs.bib}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% tabularray 常用全局设置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\SetTblrStyle&lt;/span&gt;&lt;span&gt;{firsthead}{font=&lt;/span&gt;&lt;span&gt;\bfseries&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\SetTblrStyle&lt;/span&gt;&lt;span&gt;{firstfoot}{font=&lt;/span&gt;&lt;span&gt;\small&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\SetTblrStyle&lt;/span&gt;&lt;span&gt;{middlefoot}{font=&lt;/span&gt;&lt;span&gt;\small&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\SetTblrStyle&lt;/span&gt;&lt;span&gt;{lastfoot}{font=&lt;/span&gt;&lt;span&gt;\small&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;实际使用时按需增减。宏包的加载顺序有讲究，后面”常用宏包”一节会说明。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;main.tex&lt;/code&gt; 模板&lt;a href=&quot;#maintex-模板&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;book&lt;/code&gt; 类&lt;a href=&quot;#book-类&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\documentclass&lt;/span&gt;&lt;span&gt;{book}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\input&lt;/span&gt;&lt;span&gt;{settings/packages}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\input&lt;/span&gt;&lt;span&gt;{settings/format}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\begin&lt;/span&gt;&lt;span&gt;{document}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\maketitle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\frontmatter&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\tableofcontents&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\mainmatter&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \input{contents/chap_01}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\appendix&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \input{contents/appendix}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\backmatter&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\end&lt;/span&gt;&lt;span&gt;{document}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;article&lt;/code&gt; 类&lt;a href=&quot;#article-类&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\documentclass&lt;/span&gt;&lt;span&gt;{article}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\input&lt;/span&gt;&lt;span&gt;{settings/packages}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\input&lt;/span&gt;&lt;span&gt;{settings/format}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\begin&lt;/span&gt;&lt;span&gt;{document}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\maketitle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\tableofcontents&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \input{contents/sec_01}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\appendix&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \input{contents/appendix}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\end&lt;/span&gt;&lt;span&gt;{document}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;report&lt;/code&gt; 类&lt;a href=&quot;#report-类&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\documentclass&lt;/span&gt;&lt;span&gt;{report}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\input&lt;/span&gt;&lt;span&gt;{settings/packages}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\input&lt;/span&gt;&lt;span&gt;{settings/format}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\begin&lt;/span&gt;&lt;span&gt;{document}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\maketitle&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\tableofcontents&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \input{contents/chap_01}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\appendix&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \input{contents/appendix}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\end&lt;/span&gt;&lt;span&gt;{document}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;三者差在哪&lt;a href=&quot;#三者差在哪&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;article&lt;/code&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;report&lt;/code&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;book&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;最高章节&lt;/td&gt;&lt;td&gt;&lt;code&gt;\section&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;\chapter&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;\chapter&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;默认排版&lt;/td&gt;&lt;td&gt;单面&lt;/td&gt;&lt;td&gt;单面&lt;/td&gt;&lt;td&gt;双面&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;标题页&lt;/td&gt;&lt;td&gt;正文内&lt;/td&gt;&lt;td&gt;独立一页&lt;/td&gt;&lt;td&gt;独立一页&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;\frontmatter&lt;/code&gt; / &lt;code&gt;\backmatter&lt;/code&gt;&lt;/td&gt;&lt;td&gt;无&lt;/td&gt;&lt;td&gt;无&lt;/td&gt;&lt;td&gt;有&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;典型用途&lt;/td&gt;&lt;td&gt;短文、笔记、报告&lt;/td&gt;&lt;td&gt;课程报告、毕业论文&lt;/td&gt;&lt;td&gt;书籍、学位论文&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;code&gt;report&lt;/code&gt; 和 &lt;code&gt;book&lt;/code&gt; 都有 &lt;code&gt;\chapter&lt;/code&gt;，区别主要在排版方式和文档结构命令。&lt;code&gt;book&lt;/code&gt; 多了 &lt;code&gt;\frontmatter&lt;/code&gt;、&lt;code&gt;\mainmatter&lt;/code&gt;、&lt;code&gt;\backmatter&lt;/code&gt;，默认双面排版，适合正式出版物。&lt;code&gt;report&lt;/code&gt; 默认单面，没有前言/正文/后记的划分，适合不需要装订的长文档。&lt;/p&gt;&lt;p&gt;&lt;code&gt;article&lt;/code&gt; 没有 &lt;code&gt;\chapter&lt;/code&gt;，从 &lt;code&gt;\section&lt;/code&gt; 开始。写短文、笔记够用了。&lt;/p&gt;&lt;p&gt;三者的标题页也不同。&lt;code&gt;book&lt;/code&gt; 和 &lt;code&gt;report&lt;/code&gt; 的 &lt;code&gt;\maketitle&lt;/code&gt; 都是独立一页，&lt;code&gt;article&lt;/code&gt; 的只是在正文开头插一行。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;工具简介&lt;a href=&quot;#工具简介&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在看 &lt;code&gt;.latexmkrc&lt;/code&gt; 之前，先简单介绍一下涉及的几个工具。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;XeLaTeX&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;LaTeX 的编译引擎之一。相比传统的 pdfLaTeX，XeLaTeX 原生支持 Unicode 和系统字体（通过 fontspec），处理中文要方便得多。如果你主要写中文文档，XeLaTeX（或 LuaLaTeX）基本是必选的。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;latexmk&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;一个 Perl 写的自动化编译工具。LaTeX 的编译经常需要跑多遍（交叉引用、目录、参考文献），手动执行很烦。latexmk 会自动判断需要跑几遍、要不要调用 BibTeX/Biber，直到所有引用都稳定为止。配合 &lt;code&gt;-pvc&lt;/code&gt; 参数还能监控文件变化，保存即编译。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;BibTeX 和 Biber&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;两者都是处理参考文献的工具。BibTeX 是老方案，Biber 是配合 biblatex 宏包的新方案，功能更强（支持 Unicode、更灵活的样式定制）。latexmk 的 &lt;code&gt;$bibtex_use = 2&lt;/code&gt; 会自动检测你用的是哪个，不用手动切换。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;SyncTeX&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在编辑器和 PDF 阅读器之间做双向跳转的数据格式。加了 &lt;code&gt;-synctex=1&lt;/code&gt; 之后，你可以在编辑器里点一个位置，PDF 阅读器自动翻到对应页面，反过来也行。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;.latexmkrc&lt;/code&gt;&lt;a href=&quot;#latexmkrc&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 主文件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$root&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&apos;main.tex&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 输出目录&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$out_dir&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&apos;build&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$aux_dir&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&apos;build&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 编译引擎: XeLaTeX&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$pdf_mode&lt;/span&gt;&lt;span&gt; = 5;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$xelatex&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&apos;xelatex -interaction=nonstopmode -file-line-error -synctex=1 %O %S&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 自动运行 BibTeX 或 Biber&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$bibtex_use&lt;/span&gt;&lt;span&gt; = 2;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 清理时保留 PDF&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$clean_ext&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&apos;aux bbl bcf blg fdb_latexmk fls log out run.xml synctex.gz toc&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 跨平台 PDF 预览器&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; ($^O &lt;/span&gt;&lt;span&gt;eq&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;MSWin32&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;$pdf_previewer&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&apos;start %S&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;elsif&lt;/span&gt;&lt;span&gt; ($^O &lt;/span&gt;&lt;span&gt;eq&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;darwin&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;$pdf_previewer&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&apos;open %S&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;$pdf_previewer&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&apos;xdg-open %S 2&amp;gt;/dev/null || evince %S&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Windows 下清理时不删除被占用的文件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; ($^O &lt;/span&gt;&lt;span&gt;eq&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;MSWin32&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;$cleanup_includes_generated&lt;/span&gt;&lt;span&gt; = 0;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 自定义依赖示例（取消注释即可用）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# add_cus_dep(&apos;glo&apos;, &apos;gls&apos;, 0, &apos;makeglos&apos;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# sub makeglos {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#     system(&quot;makeindex -s $_[0].ist -t $_[0].glg -o $_[0].gls $_[0].glo&quot;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;section&gt;&lt;h3&gt;逐项说明&lt;a href=&quot;#逐项说明&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$root&lt;/code&gt;：主文件名。latexmk 从这里开始分析依赖。改成你的实际文件名就行。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$out_dir&lt;/code&gt; / &lt;code&gt;$aux_dir&lt;/code&gt;：编译产物放哪。设成 &lt;code&gt;build&lt;/code&gt; 能让根目录干净一些，PDF 会出现在 &lt;code&gt;build/main.pdf&lt;/code&gt;。记得在 &lt;code&gt;.gitignore&lt;/code&gt; 里加上 &lt;code&gt;build/&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$pdf_mode&lt;/code&gt;：选择编译引擎。1 是 pdfLaTeX，4 是 LuaLaTeX，5 是 XeLaTeX。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$xelatex&lt;/code&gt;：XeLaTeX 的调用参数。&lt;code&gt;-interaction=nonstopmode&lt;/code&gt; 让它遇到错误继续编译而不是停下来等你按回车；&lt;code&gt;-file-line-error&lt;/code&gt; 让报错信息带上文件名和行号；&lt;code&gt;-synctex=1&lt;/code&gt; 生成编辑器跳转用的数据。&lt;code&gt;%O&lt;/code&gt; 和 &lt;code&gt;%S&lt;/code&gt; 是 latexmk 的占位符，分别代表额外选项和源文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$bibtex_use&lt;/code&gt;：设为 2 就行，自动检测 BibTeX 还是 Biber。设为 0 关闭自动处理，1 只在 &lt;code&gt;.bbl&lt;/code&gt; 已存在时才跑。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$clean_ext&lt;/code&gt;：&lt;code&gt;latexmk -c&lt;/code&gt; 会删掉这些后缀的文件。PDF 不在里面，所以清理后 PDF 还在。注意区分 &lt;code&gt;-c&lt;/code&gt;（清理中间文件，保留 PDF）和 &lt;code&gt;-C&lt;/code&gt;（连 PDF 一起删），别手滑用了 &lt;code&gt;-C&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨平台预览器&lt;/strong&gt;：Perl 的 &lt;code&gt;$^O&lt;/code&gt; 变量存着操作系统类型：Windows 是 &lt;code&gt;MSWin32&lt;/code&gt;，macOS 是 &lt;code&gt;darwin&lt;/code&gt;，Linux 就是 &lt;code&gt;linux&lt;/code&gt;。&lt;code&gt;start&lt;/code&gt;、&lt;code&gt;open&lt;/code&gt;、&lt;code&gt;xdg-open&lt;/code&gt; 分别是三个平台打开文件的命令。&lt;code&gt;%S&lt;/code&gt; 会被替换成 PDF 路径。配合 &lt;code&gt;latexmk -pvc&lt;/code&gt; 使用，保存文件后自动编译并刷新 PDF 预览，不用手动跑命令。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$cleanup_includes_generated&lt;/code&gt;：Windows 上文件锁比较死板，latexmk 清理时可能删不掉正在被占用的文件。设为 0 可以避免这个问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自定义依赖&lt;/strong&gt;：&lt;code&gt;add_cus_dep&lt;/code&gt; 用来告诉 latexmk “如果某个 &lt;code&gt;.glo&lt;/code&gt; 文件变了，要重新生成对应的 &lt;code&gt;.gls&lt;/code&gt;”。示例里是术语表的场景，取消注释就能用。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常用宏包&lt;a href=&quot;#常用宏包&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;以下是写中文 LaTeX 文档时经常会用到的宏包，以及它们的典型配置。&lt;/p&gt;&lt;p&gt;加载顺序需要注意几点：&lt;code&gt;fontspec&lt;/code&gt; 和 &lt;code&gt;xeCJK&lt;/code&gt; 要在大多数宏包之前加载；&lt;code&gt;amsmath&lt;/code&gt; 要在 &lt;code&gt;mathtools&lt;/code&gt; 之前；&lt;code&gt;tabularray&lt;/code&gt; 的 &lt;code&gt;\UseTblrLibrary&lt;/code&gt; 要在 &lt;code&gt;siunitx&lt;/code&gt; 之后（因为 &lt;code&gt;siunitx&lt;/code&gt; 库需要 siunitx 已加载）；&lt;code&gt;hyperref&lt;/code&gt; 几乎总是放在最后，唯一的例外是 &lt;code&gt;cleveref&lt;/code&gt;，它必须在 &lt;code&gt;hyperref&lt;/code&gt; 之后。上面 &lt;code&gt;packages.tex&lt;/code&gt; 示例中的顺序可以直接用。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;字体与编码&lt;a href=&quot;#字体与编码&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;fontspec&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;配合 XeLaTeX/LuaLaTeX 使用，用来设置系统字体。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{fontspec}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setmainfont&lt;/span&gt;&lt;span&gt;{TeX Gyre Termes}          &lt;/span&gt;&lt;span&gt;% 正文衬线字体&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setsansfont&lt;/span&gt;&lt;span&gt;{TeX Gyre Heros}           &lt;/span&gt;&lt;span&gt;% 无衬线字体&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setmonofont&lt;/span&gt;&lt;span&gt;{TeX Gyre Cursor}          &lt;/span&gt;&lt;span&gt;% 等宽字体&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;xeCJK&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;处理中文排版的核心宏包，自动处理中英文之间的间距、标点挤压等。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{xeCJK}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setCJKmainfont&lt;/span&gt;&lt;span&gt;{Source Han Serif SC}   &lt;/span&gt;&lt;span&gt;% 中文衬线（思源宋体）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setCJKsansfont&lt;/span&gt;&lt;span&gt;{Source Han Sans SC}    &lt;/span&gt;&lt;span&gt;% 中文无衬线（思源黑体）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setCJKmonofont&lt;/span&gt;&lt;span&gt;{Source Han Mono SC}    &lt;/span&gt;&lt;span&gt;% 中文等宽&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;页面布局&lt;a href=&quot;#页面布局&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;geometry&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;控制页面尺寸和边距，比 LaTeX 默认的布局参数好用得多。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;[a4paper, margin=2.5cm]{geometry}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% 也可以精细控制&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \usepackage[a4paper, top=3cm, bottom=2.5cm, left=2.8cm, right=2.2cm]{geometry}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;fancyhdr&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;自定义页眉页脚。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{fancyhdr}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\pagestyle&lt;/span&gt;&lt;span&gt;{fancy}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\fancyhf&lt;/span&gt;&lt;span&gt;{}                             &lt;/span&gt;&lt;span&gt;% 清空默认设置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\fancyhead&lt;/span&gt;&lt;span&gt;[L]{&lt;/span&gt;&lt;span&gt;\leftmark&lt;/span&gt;&lt;span&gt;}               &lt;/span&gt;&lt;span&gt;% 页眉左侧放章节名&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\fancyfoot&lt;/span&gt;&lt;span&gt;[C]{&lt;/span&gt;&lt;span&gt;\thepage&lt;/span&gt;&lt;span&gt;}                &lt;/span&gt;&lt;span&gt;% 页脚中间放页码&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\renewcommand&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;\headrulewidth&lt;/span&gt;&lt;span&gt;}{0.4pt}   &lt;/span&gt;&lt;span&gt;% 页眉横线粗细&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;数学与代码&lt;a href=&quot;#数学与代码&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;amsmath / amssymb / amsfonts&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;数学排版的基础三件套，基本上写公式就得用。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{amsmath}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{amssymb}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{amsfonts}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;mathtools&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;amsmath 的扩展包，修复了一些设计问题并增加了实用工具。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{mathtools}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% 例如：可以给公式加编号标签&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \usetagform{default}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;listings&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;排版代码块。支持语法高亮，可以指定语言和样式。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{listings}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\lstset&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;basicstyle=&lt;/span&gt;&lt;span&gt;\ttfamily\small&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;breaklines=true,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;frame=single,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;numbers=left,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;numberstyle=&lt;/span&gt;&lt;span&gt;\tiny\color&lt;/span&gt;&lt;span&gt;{gray},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;keywordstyle=&lt;/span&gt;&lt;span&gt;\color&lt;/span&gt;&lt;span&gt;{blue},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;commentstyle=&lt;/span&gt;&lt;span&gt;\color&lt;/span&gt;&lt;span&gt;{green!60!black},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;stringstyle=&lt;/span&gt;&lt;span&gt;\color&lt;/span&gt;&lt;span&gt;{red!70!black},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;minted&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;基于 Pygments 的代码排版，高亮效果比 listings 好，但需要 Python 环境且编译时要加 &lt;code&gt;-shell-escape&lt;/code&gt;。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{minted}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% 使用示例：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \begin{minted}{python}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% def hello():&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%     print(&quot;world&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \end{minted}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;图表与浮动体&lt;a href=&quot;#图表与浮动体&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;graphicx&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;插入图片。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{graphicx}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\graphicspath&lt;/span&gt;&lt;span&gt;{{figs/}}                 &lt;/span&gt;&lt;span&gt;% 指定图片搜索路径&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;tabularray&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;现代表格宏包，统一了表格的样式配置和内容分离。用 &lt;code&gt;tblr&lt;/code&gt; 环境替代传统的 &lt;code&gt;tabular&lt;/code&gt;，样式参数写在 &lt;code&gt;\begin{tblr}{...}&lt;/code&gt; 的花括号里，正文和样式彻底分开。配合 &lt;code&gt;booktabs&lt;/code&gt; 库可以使用 &lt;code&gt;\toprule&lt;/code&gt;、&lt;code&gt;\midrule&lt;/code&gt;、&lt;code&gt;\bottomrule&lt;/code&gt;。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{tabularray}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\UseTblrLibrary&lt;/span&gt;&lt;span&gt;{booktabs, siunitx}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \begin{table}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \begin{tblr}{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   colspec = {lrc},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   row{1} = {font=\bfseries},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   \toprule&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   姓名 &amp;amp; 分数 &amp;amp; 等级 \\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   \midrule&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   张三 &amp;amp; 95 &amp;amp; A \\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   李四 &amp;amp; 82 &amp;amp; B \\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   \bottomrule&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \end{tblr}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \end{table}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;booktabs&lt;/code&gt; 库提供三线表的线条命令，&lt;code&gt;siunitx&lt;/code&gt; 库让列可以用 &lt;code&gt;S&lt;/code&gt; 类型对齐数字。tabularray 还支持 &lt;code&gt;longtblr&lt;/code&gt;（跨页长表格）和 &lt;code&gt;talltblr&lt;/code&gt;（不跨页但可加脚注的表格），基本覆盖了所有表格场景。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;subcaption&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在一个浮动体里放多张子图，每个子图独立编号和引用。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{subcaption}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \begin{figure}[htbp]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   \begin{subfigure}{0.48\textwidth}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%     \includegraphics[width=\textwidth]{fig_a}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%     \caption{子图 A}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   \end{subfigure}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   \hfill&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   \begin{subfigure}{0.48\textwidth}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%     \includegraphics[width=\textwidth]{fig_b}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%     \caption{子图 B}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   \end{subfigure}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   \caption{总标题}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \end{figure}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;参考文献&lt;a href=&quot;#参考文献&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;biblatex&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;现代的参考文献管理方案，配合 Biber 后端使用。比 BibTeX 灵活很多，支持多语言、多文献列表等。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;[backend=biber, style=gb7714-2015]{biblatex}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\addbibresource&lt;/span&gt;&lt;span&gt;{refs.bib}              &lt;/span&gt;&lt;span&gt;% 指定 .bib 文件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% 正文中用 \cite{key} 引用&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% 文末用 \printbibliography 输出&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;style=gb7714-2015&lt;/code&gt; 是中文文献常用的国标样式，需要 &lt;code&gt;biblatex-gb7714-2015&lt;/code&gt; 宏包。英文文档常用 &lt;code&gt;style=ieee&lt;/code&gt;、&lt;code&gt;style=apa&lt;/code&gt; 或 &lt;code&gt;style=alphabetic&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;hyperref&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;给交叉引用、目录、参考文献加上超链接。通常放在 &lt;code&gt;\usepackage&lt;/code&gt; 的最后（少数例外，如 cleveref 要在 hyperref 之后）。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;colorlinks=true,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;linkcolor=blue,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;citecolor=blue,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;urlcolor=blue,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;]{hyperref}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;其他实用工具&lt;a href=&quot;#其他实用工具&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;ctex&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;如果不想手动配 xeCJK，直接用 ctex 宏包或 &lt;code&gt;ctexart&lt;/code&gt;/&lt;code&gt;ctexbook&lt;/code&gt; 文档类，一键搞定中文支持。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;[UTF8, heading=true]{ctex}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% 或者直接用 \documentclass{ctexart}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;enumitem&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;定制列表环境（enumerate、itemize、description）的间距和标签样式。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{enumitem}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\setlist&lt;/span&gt;&lt;span&gt;{nosep}                        &lt;/span&gt;&lt;span&gt;% 去掉列表的多余间距&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \begin{enumerate}[label=(\arabic*)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   \item 第一项&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   \item 第二项&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \end{enumerate}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;tikz&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;画矢量图。学习曲线陡峭，但画出来的东西质量很高，跟 LaTeX 文档浑然一体。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{tikz}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usetikzlibrary&lt;/span&gt;&lt;span&gt;{arrows.meta, positioning}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% 简单示例：画一个带箭头的直线&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \begin{tikzpicture}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;%   \draw[-&amp;gt;] (0,0) -- (2,1) node[right] {$y = x/2$};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \end{tikzpicture}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;siunitx&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;处理数值和单位的排版。数字的千分位、科学记数法、单位的正体和间距，它都能管。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;{siunitx}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\sisetup&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;range-phrase = &lt;/span&gt;&lt;span&gt;\ensuremath&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;\sim&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;range-units = single,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \qty{10}{\metre}           -&amp;gt; 10 m&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \qty{1.23e5}{\pascal}      -&amp;gt; 1.23 × 10⁵ Pa&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \qtyrange{1}{10}{\metre}   -&amp;gt; 1~10 m（单位只出现一次，用 ~ 连接）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;range-units=single&lt;/code&gt; 让范围表达中单位只出现一次，而不是”1 m 到 10 m”。&lt;code&gt;range-phrase&lt;/code&gt; 设成 &lt;code&gt;\ensuremath{\sim}&lt;/code&gt; 把分隔符换成 &lt;code&gt;~&lt;/code&gt;（波浪号），&lt;code&gt;\ensuremath&lt;/code&gt; 确保它在数学模式和文本模式下都能正确渲染。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;cleveref&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;智能引用，自动加上”图&quot;&quot;表&quot;&quot;公式”等前缀，不用自己写 &lt;code&gt;\ref{fig:xxx} 的图&lt;/code&gt;。注意要放在 hyperref 之后加载。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;[capitalise]{cleveref}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% 使用 \cref{fig:example} 会自动输出&quot;图 1&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;% \cref{eq:main,eq:sub} 会输出&quot;公式 (1) 和 (2)&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>设备树入门：从第一份 DTS 到 Linux 驱动匹配</title><link>https://bfmhno3.github.io/posts/devicetree-study-notes/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/devicetree-study-notes/</guid><description>我从一份可编译的最小 DTS 开始，走完 DTS、DTB、boot program 到 Linux 驱动匹配的链路，再用真实工具和内核调试路径验证设备树。</description><pubDate>Wed, 20 May 2026 03:15:00 GMT</pubDate><content:encoded>&lt;p&gt;我以前把设备树当成一堆需要背下来的属性名：&lt;code&gt;compatible&lt;/code&gt;、&lt;code&gt;reg&lt;/code&gt;、&lt;code&gt;interrupts&lt;/code&gt;，还有几个看起来像预处理器产物的 &lt;code&gt;#*-cells&lt;/code&gt;。后来我决定只做一件事：写一份足够小的 DTS，把它编译成 DTB，再从 Linux 看到它实际收到的内容。设备树一下从名词表变成了一条可以跑通的交接链路。&lt;/p&gt;
&lt;p&gt;本文不绑定具体开发板。我们会用一个虚构但自洽的 SoC 和 UART 做实验，所以这份 DTB 不会神奇地让你的电脑多出一个串口（遗憾，但也算避免了一次售后事故）。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;设备树到底解决什么问题？&lt;a href=&quot;#设备树到底解决什么问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;PCI 和 USB 这类总线可以通过协议探测设备。Linux 询问总线，设备回答自己的身份，操作系统再加载驱动。SoC 内部的 UART、GPIO、定时器和很多 I2C 设备通常没有这样的自发现协议。CPU 只知道某个地址范围可能连着寄存器，却不知道那里是什么设备，或者这根中断线接到了哪里。&lt;/p&gt;&lt;p&gt;设备树把这份外部知识交给启动链路。把它想成搬家时交接的一张机器清单：清单说明房间里有什么、门牌号是什么、钥匙交给谁；它不会替你操作洗衣机。对应到规范术语，boot program 可以是固件、bootloader 或 hypervisor，client program 可以是 Linux，也可以是另一个 bootloader。boot program 把 devicetree 放进内存，然后把它传给 client program。&lt;/p&gt;&lt;p&gt;因此职责边界很重要：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DTS/DTB&lt;/strong&gt; 描述硬件的拓扑、资源和连接关系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;boot program&lt;/strong&gt; 加载 DTB，并把它传给下一个程序。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux 驱动&lt;/strong&gt; 根据描述找到资源，映射寄存器，申请中断，然后真正操作硬件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;binding&lt;/strong&gt; 规定某类设备允许使用哪些 &lt;code&gt;compatible&lt;/code&gt; 和属性，是接口约束，不是驱动实现。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;设备树不是硬件扫描器，也不是驱动代码。往 &lt;code&gt;reg&lt;/code&gt; 里写一个地址，不会自动生成对该地址执行读写的驱动。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;注意：设备树不会替代驱动。&lt;/strong&gt; &lt;code&gt;compatible&lt;/code&gt; 只负责匹配，&lt;code&gt;reg&lt;/code&gt; 只描述资源。只有对应的 Linux 驱动成功匹配并执行 &lt;code&gt;probe()&lt;/code&gt;，设备才可能被初始化。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;先跑通一条 DTS 到 DTB 的链路&lt;a href=&quot;#先跑通一条-dts-到-dtb-的链路&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我们先写一个只描述 UART 的最小例子。这里的地址 &lt;code&gt;0x10000000&lt;/code&gt; 是实验数据，不属于任何真实芯片。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;dts&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;v1&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;compatible &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;example,hello-board&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;model &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Example Hello Board&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#address&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#size&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;soc {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;compatible &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;simple-bus&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#address&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#size&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ranges;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;uart0: serial@&lt;/span&gt;&lt;span&gt;10000000&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;compatible &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;example,uart&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;reg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;10000000&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;status &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;okay&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;保存为 &lt;code&gt;/tmp/devicetree-tutorial.dts&lt;/code&gt; 后，先确认工具是否存在：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;dtc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--version&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;dtc&lt;/code&gt; 是 Device Tree Compiler。它把 DTS 文本转换成 DTB，也能把 DTB 反编译回近似的 DTS。我的实验环境如果没有 &lt;code&gt;dtc&lt;/code&gt;，这一步就应该明确停下来安装工具，而不是编造一段”成功输出”。在 Debian/Ubuntu 上通常由 &lt;code&gt;device-tree-compiler&lt;/code&gt; 包提供，其他发行版请查自己的包管理器。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Caution&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;工具检查。&lt;/strong&gt; 如果 &lt;code&gt;dtc&lt;/code&gt; 不在 &lt;code&gt;PATH&lt;/code&gt; 中，请先安装 Device Tree Compiler。后续编译、反编译和 magic 检查都依赖它们；不要把”命令没有执行”写成”实验成功”。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;编译命令的输入是 DTS，输出是 DTB：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;dtc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-I&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-O&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dtb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/tmp/devicetree-tutorial.dtb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;/tmp/devicetree-tutorial.dts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;随后反编译：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;dtc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-I&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dtb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-O&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/tmp/devicetree-tutorial.roundtrip.dts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;/tmp/devicetree-tutorial.dtb&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;反编译结果不是原文件的文本复制品。标签名、注释和部分源码排版可能消失，但根节点、&lt;code&gt;compatible&lt;/code&gt;、&lt;code&gt;reg&lt;/code&gt; 和 &lt;code&gt;status&lt;/code&gt; 等设备树数据应该还在。若系统安装了 &lt;code&gt;fdtdump&lt;/code&gt;，还可以查看底层结构；没有它时，至少可以观察 DTB 的头部：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;hexdump&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-C&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/tmp/devicetree-tutorial.dtb&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;合法 DTB 的前四个字节按大端解释为 magic &lt;code&gt;0xd00dfeed&lt;/code&gt;。这不是”文件前 40 字节永远是什么”的承诺，后面讲 FDT 结构时会看到，真正可靠的是头部字段里的偏移和长度。&lt;/p&gt;&lt;p&gt;这里的”大端”说的是&lt;strong&gt;多字节数值在内存或文件中的字节排列顺序&lt;/strong&gt;。例如数值 &lt;code&gt;0x12345678&lt;/code&gt; 由四个字节 &lt;code&gt;12 34 56 78&lt;/code&gt; 组成：大端把高位字节 &lt;code&gt;12&lt;/code&gt; 放在低地址一侧，小端则把低位字节 &lt;code&gt;78&lt;/code&gt; 放在低地址一侧。设备树规范要求 DTB 中的 cell 按大端编码，所以用十六进制查看原始字节时，不能直接把字节序列当作当前 CPU 的本地整数来读。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;大小端提醒。&lt;/strong&gt; &lt;code&gt;0xd00dfeed&lt;/code&gt; 是按大端解释出的 32 位 magic。看到原始字节时，先确认协议规定的字节序，再解释数值；否则很容易把同一组字节读成另一个整数。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;这份小树逐行在说什么？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;code&gt;/dts-v1/;&lt;/code&gt; 声明 DTS source format version 1。&lt;code&gt;/ { ... };&lt;/code&gt; 是根节点，根节点没有 &lt;code&gt;@unit-address&lt;/code&gt;。&lt;code&gt;model&lt;/code&gt; 面向人，&lt;code&gt;compatible&lt;/code&gt; 面向软件匹配；根节点的 &lt;code&gt;compatible&lt;/code&gt; 也必须来自对应平台的约定，实验值只是实验值。&lt;/p&gt;&lt;p&gt;&lt;code&gt;soc&lt;/code&gt; 是一个总线节点。&lt;code&gt;ranges;&lt;/code&gt; 是空属性，在常见的 &lt;code&gt;simple-bus&lt;/code&gt; 写法里表示子总线地址空间与父地址空间相同。&lt;code&gt;uart0:&lt;/code&gt; 是源码标签，只服务于 DTS 内部引用，标签本身不会作为标签字符串写入 DTB。&lt;/p&gt;&lt;p&gt;&lt;code&gt;serial@10000000&lt;/code&gt; 由两部分构成：通用节点名 &lt;code&gt;serial&lt;/code&gt; 和 unit-address &lt;code&gt;10000000&lt;/code&gt;。因为节点有 &lt;code&gt;reg&lt;/code&gt;，unit-address 必须与 &lt;code&gt;reg&lt;/code&gt; 中的第一个地址相匹配。节点名应使用 1 到 31 个合法字符，并以字母开头；具体总线 binding 还可能增加额外要求。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;DTS 语法的最小心智模型&lt;a href=&quot;#dts-语法的最小心智模型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;节点是树，属性是节点里的键值对。属性要写在子节点之前，子节点再继续向下展开。下面把常用值类型放在一处：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;empty&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;property;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;one&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cell &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;42&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;two&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;00000001&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;00000000&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;one&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;string &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;hello&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;list &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;example,device&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;example,bus&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;bytes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;11&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;22&lt;/span&gt;&lt;span&gt; aa];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;尖括号里的每个 cell 是 32 位无符号整数。一个 64 位值由两个 cell 表示，高位在前。方括号表示 bytestring。多个逗号分隔的组件会拼接成同一个属性值。DTS 还支持 C 风格的算术、位运算和逻辑表达式，例如 &lt;code&gt;reg = &amp;lt;(0x1000 + 0x200) 0x100&amp;gt;;&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;code&gt;reg&lt;/code&gt; 不是一种到处一样的”地址加长度”语法。它的 cell 布局由&lt;strong&gt;父节点&lt;/strong&gt;的 &lt;code&gt;#address-cells&lt;/code&gt; 和 &lt;code&gt;#size-cells&lt;/code&gt; 决定：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;mmio&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;bus {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#address&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#size&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;device@&lt;/span&gt;&lt;span&gt;2000&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;reg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;2000&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;i2c&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;bus {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#address&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#size&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;eeprom@&lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;reg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;在第一个例子里，每个条目是一个地址 cell 加一个长度 cell。第二个例子里，I2C 子设备的 &lt;code&gt;reg&lt;/code&gt; 是从设备地址，父总线把 &lt;code&gt;#size-cells&lt;/code&gt; 设为 0，所以没有长度 cell。不要从某块板子的数值推导普适规则；先看父节点，再看该总线和设备的 binding。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Important&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;reg&lt;/code&gt; 的关键规则。&lt;/strong&gt; 永远先看父节点的 &lt;code&gt;#address-cells&lt;/code&gt; 和 &lt;code&gt;#size-cells&lt;/code&gt;，再按照对应 bus binding 解释 &lt;code&gt;reg&lt;/code&gt;。I2C 从设备地址、MMIO 地址和 PCI 地址不是同一种语义。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ranges&lt;/code&gt; 描述子总线地址空间到父总线地址空间的翻译。它不是 &lt;code&gt;reg&lt;/code&gt; 的别名。&lt;code&gt;dma-ranges&lt;/code&gt; 则描述 DMA 地址视角下的范围，是否需要它取决于平台的 DMA 地址转换。实际填写时必须查 binding 和平台 DTS。&lt;/p&gt;&lt;p&gt;标签和引用让设备树可以把物理层级与逻辑连接分开：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;intc: interrupt&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;controller {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;compatible &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;example,intc&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;interrupt&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;controller;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#interrupt&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;serial@&lt;/span&gt;&lt;span&gt;10000000&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;interrupt&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;parent &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span&gt;intc&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;interrupts &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;&amp;amp;intc&lt;/code&gt; 最终会被编码为 phandle 相关信息。&lt;code&gt;&amp;amp;node { ... };&lt;/code&gt; 是对已经定义节点的覆盖，板级 &lt;code&gt;.dts&lt;/code&gt; 经常用它启用 SoC &lt;code&gt;.dtsi&lt;/code&gt; 中默认禁用的外设：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;uart0 {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;status &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;okay&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;共享定义通常放进 &lt;code&gt;.dtsi&lt;/code&gt;，再通过 &lt;code&gt;/include/ &quot;soc.dtsi&quot;&lt;/code&gt; 引入。源码标签只存在于 DTS 语言；DTB 里保存的是供运行时解析的结构和 phandle 值。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;描述一个真实设备需要回答的问题&lt;a href=&quot;#描述一个真实设备需要回答的问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我写设备节点时会按下面的顺序检查，而不是看到属性名就往里塞：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;设备是谁？&lt;/strong&gt; 用 binding 规定的 &lt;code&gt;compatible&lt;/code&gt;。列表通常从特化到通用，但只有当内核确实存在对应匹配项时，回退字符串才有意义。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源在哪里？&lt;/strong&gt; 用该总线语义下的 &lt;code&gt;reg&lt;/code&gt;、&lt;code&gt;ranges&lt;/code&gt;、内存区域和时钟资源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设备怎么连？&lt;/strong&gt; 用 phandle 引用表达时钟、reset、GPIO、DMA 和中断控制器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;现在是否使用？&lt;/strong&gt; 通常通过 binding 允许的 &lt;code&gt;status&lt;/code&gt; 值表达，常见值是 &lt;code&gt;okay&lt;/code&gt; 和 &lt;code&gt;disabled&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;一个稍微完整的 SoC 节点可能长这样：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;soc {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;compatible &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;simple-bus&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#address&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#size&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ranges;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;uart0: serial@&lt;/span&gt;&lt;span&gt;10000000&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;compatible &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;example,uart-v2&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;example,uart&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;reg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;10000000&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;interrupts&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;extended &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span&gt;intc &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;clocks &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span&gt;clk &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;resets &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span&gt;reset &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pinctrl&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;names &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;default&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pinctrl&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span&gt;uart0_pins&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;dmas &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span&gt;dma &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;status &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;okay&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这段代码只是展示属性之间的关系，不是某个可直接提交的 Linux binding。&lt;code&gt;interrupts-extended&lt;/code&gt; 的 cell 数量由被引用的中断控制器定义，&lt;code&gt;clocks&lt;/code&gt;、&lt;code&gt;resets&lt;/code&gt; 和 &lt;code&gt;dmas&lt;/code&gt; 的参数也分别由 provider 的 &lt;code&gt;#*-cells&lt;/code&gt; 和对应 binding 定义。设备树不是”把所有可能的属性都填上”竞赛。&lt;/p&gt;&lt;p&gt;&lt;code&gt;compatible&lt;/code&gt; 列表的顺序有意义：更特化的字符串在前，更通用的字符串在后。它不是承诺任意平台都存在一个通用回退驱动。正确流程是先查 Linux 内核 &lt;code&gt;Documentation/devicetree/bindings/&lt;/code&gt; 下的 YAML，再决定字符串和必需属性。&lt;code&gt;make dtbs_check&lt;/code&gt; 会用这些 schema 检查编译后的设备树，但 schema 检查通过也不等于硬件接线正确。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;不要凭感觉填写属性。&lt;/strong&gt; &lt;code&gt;compatible&lt;/code&gt;、中断 specifier、clock/reset/DMA 参数都由 binding 规定。&lt;code&gt;make dtbs_check&lt;/code&gt; 通过，只说明描述符合 schema，不证明芯片接线、时钟或电源真的正确。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;I2C 设备和中断：两棵逻辑树&lt;a href=&quot;#i2c-设备和中断两棵逻辑树&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I2C 触摸控制器在设备树的物理拓扑中是 I2C 控制器的子节点，但它的中断线可能接到 GPIO 控制器，再由 GPIO 控制器连接到 GIC。于是”设备在哪”和”中断怎么走”是两种不同的关系。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;i2c0 {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#address&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#size&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;touch@&lt;/span&gt;&lt;span&gt;38&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;compatible &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;example,touch&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;reg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;38&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;interrupt&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;parent &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span&gt;gpio0&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;interrupts &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;12&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;gpio0: gpio&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;controller {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;compatible &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;example,gpio&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;gpio&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;controller;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#gpio&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;interrupt&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;controller;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#interrupt&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;interrupt&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;parent &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span&gt;gic&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;interrupt-controller;&lt;/code&gt; 是空属性，表示该节点提供中断域。&lt;code&gt;#interrupt-cells&lt;/code&gt; 告诉消费者一个 interrupt specifier 有几个 cell，但具体每个 cell 的含义不能猜。ARM GIC、GPIO 控制器和其他中断域的数量可能不同。&lt;/p&gt;&lt;p&gt;简单设备通常写 &lt;code&gt;interrupt-parent&lt;/code&gt; 和 &lt;code&gt;interrupts&lt;/code&gt;。如果设备连接多个中断控制器，可以使用 &lt;code&gt;interrupts-extended&lt;/code&gt;，它把每个中断控制器的 phandle 和对应 specifier 放在一起。复杂的 PCI host bridge 等 nexus 节点还会使用 &lt;code&gt;interrupt-map&lt;/code&gt; 做域间翻译；入门阶段只需要记住：中断引用可以跨越设备树的物理父子关系，详细格式必须回到 binding。&lt;/p&gt;&lt;p&gt;GPIO、clock、reset、DMA 的写法也是同一类 phandle 思路：消费者引用 provider，provider 通过 &lt;code&gt;#gpio-cells&lt;/code&gt; 等属性声明 specifier 的宽度。真正的 flags 和参数解释来自各自 binding，不来自属性名字的直觉。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Linux 收到 DTB 后发生什么？&lt;a href=&quot;#linux-收到-dtb-后发生什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;bootloader 把 DTB 地址交给内核后，Linux 解析扁平化设备树，建立自己的 device model。对于 &lt;code&gt;simple-bus&lt;/code&gt; 下的 platform 设备，内核会根据节点创建设备对象；驱动注册时提供 &lt;code&gt;of_match_table&lt;/code&gt;，其中的 &lt;code&gt;compatible&lt;/code&gt; 与节点属性进行匹配。&lt;/p&gt;&lt;p&gt;匹配成功以后，驱动才会读取资源。常见的 platform driver 代码大致是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; of_device_id example_uart_of_match&lt;/span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ .compatible &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;example,uart-v2&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ .compatible &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;example,uart&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;example_uart_probe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; platform_device &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt;pdev&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; resource &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;mem;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; __iomem &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;base;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; irq;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mem &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;platform_get_resource&lt;/span&gt;&lt;span&gt;(pdev, IORESOURCE_MEM, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;base &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;devm_ioremap_resource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;pdev&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span&gt;, mem);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;IS_ERR&lt;/span&gt;&lt;span&gt;(base))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PTR_ERR&lt;/span&gt;&lt;span&gt;(base);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;irq &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;platform_get_irq&lt;/span&gt;&lt;span&gt;(pdev, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (irq &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; irq;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;devm_request_irq&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;pdev&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span&gt;, irq, example_uart_irq,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                            &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;dev_name&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;pdev&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span&gt;), pdev);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这里的关键不是背 API，而是看清边界：设备树提供资源描述，驱动通过 Linux API 取得并使用资源。&lt;code&gt;devm_ioremap_resource()&lt;/code&gt; 不会验证地址背后真的焊了一块 UART；它只负责在内核资源模型允许的前提下建立映射。&lt;code&gt;devm_request_irq()&lt;/code&gt; 也不替你修复接错的中断线。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;排错边界。&lt;/strong&gt; 设备树描述”有什么”和”资源在哪里”，驱动决定”怎么操作”。匹配失败、资源获取失败和硬件本身不工作，应分别从 &lt;code&gt;compatible&lt;/code&gt;、资源属性以及时钟/复位/pinmux/接线排查。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;因此排错时要区分三类失败：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;compatible&lt;/code&gt; 不匹配：设备可能根本没有进入目标驱动的 &lt;code&gt;probe()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reg&lt;/code&gt;、clock、reset 或中断描述错误：驱动进入 &lt;code&gt;probe()&lt;/code&gt;，但获取资源失败。&lt;/li&gt;
&lt;li&gt;DTS 和 binding 都正确，硬件仍不工作：继续检查时钟、复位、pinmux、电源和真实接线。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;系统节点：不要把惯例写成绝对规则&lt;a href=&quot;#系统节点不要把惯例写成绝对规则&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;完整系统描述通常还会出现这些节点：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;chosen {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;stdout&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;path &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;uart0;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;aliases {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;serial0 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;uart0;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cpus {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#address&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;#size&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cpu@&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;device_type &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;cpu&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;reg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;memory@&lt;/span&gt;&lt;span&gt;80000000&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;device_type &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;memory&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;reg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;80000000&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;40000000&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;/chosen&lt;/code&gt; 是 boot program 向 client program 传递运行时选择的地方，例如 &lt;code&gt;bootargs&lt;/code&gt; 和 &lt;code&gt;stdout-path&lt;/code&gt;。&lt;code&gt;/aliases&lt;/code&gt; 为长路径提供短名字。&lt;code&gt;/cpus&lt;/code&gt; 描述 CPU 集合，&lt;code&gt;/memory&lt;/code&gt; 描述可用 RAM。某种架构和启动方式可能要求这些节点，但不能脱离上下文说所有设备树在任何场景都必须有完全相同的节点。&lt;/p&gt;&lt;p&gt;&lt;code&gt;/reserved-memory&lt;/code&gt; 节点和 FDT 的 memory reservation block 也不是同一个东西。前者是设备树结构里的内存区域描述，常用于 CMA、framebuffer 或固件共享内存；后者是 DTB 二进制中的底层保留表，boot program 可以用它避免 client program 覆写仍在使用的区域。两者都和”这段内存不能随便拿来用”有关，但出现的阶段和表达层次不同。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;从 Linux 现场反查设备树&lt;a href=&quot;#从-linux-现场反查设备树&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;拿到一台正在运行的 Linux 后，我会按这个顺序排查，而不是先盯着驱动源码：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;查看内核实际展开的设备树：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ls&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/sys/firmware/devicetree/base&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ls&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/proc/device-tree&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;两个路径通常指向内核提供的设备树视图，具体挂载方式取决于系统配置。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看某个节点的属性。字符串属性以 NUL 结尾，不能总用普通文本工具直读：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;tr&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;\0&apos;&lt;/span&gt;&lt;span&gt; &amp;lt; &lt;/span&gt;&lt;span&gt;/sys/firmware/devicetree/base/model&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;\n&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;xxd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/sys/firmware/devicetree/base/soc/serial@10000000/reg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看驱动和设备的启动日志：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;dmesg&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-E&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;of|firmware|uart|probe&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果手里只有 DTB，先反编译，再把结果和板级 &lt;code&gt;.dts&lt;/code&gt;、SoC &lt;code&gt;.dtsi&lt;/code&gt; 以及对应 YAML binding 对照。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 Linux 内核源码树中运行 schema 检查：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;make&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dtbs_check&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;code&gt;dtbs_check&lt;/code&gt; 主要检查设备树是否符合 binding schema。它不能替代 &lt;code&gt;dtc&lt;/code&gt; 的语法检查，也不能替代示波器、逻辑分析仪或一双确认过连接器的眼睛。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Tip&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;验证顺序。&lt;/strong&gt; 先看 Linux 实际展开的设备树，再看 &lt;code&gt;dmesg&lt;/code&gt;，然后对照 DTB、板级 DTS 和 binding。&lt;code&gt;dtbs_check&lt;/code&gt; 是约束检查，不是硬件功能测试。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;DTB 为什么可以被搬到别的地址？&lt;a href=&quot;#dtb-为什么可以被搬到别的地址&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;DTS 是源码，DTB 是给 client program 消费的紧凑二进制表示。FDT 的设计目标之一是让 boot program 能把它放入内存并传递给下一个程序，因此结构内部使用偏移和长度，而不是依赖加载地址的 C 裸指针。&lt;/p&gt;&lt;p&gt;DTB 头部包含 magic、总大小，以及 memory reservation block、structure block 和 strings block 的偏移与大小等字段。字段按规范使用大端编码。解析器应该读取这些字段，而不是假设某个区块永远位于固定位置。&lt;/p&gt;&lt;p&gt;structure block 把树线性化为 token 序列，常见 token 包括：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FDT_BEGIN_NODE&lt;/code&gt;：进入节点，后面跟节点名。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FDT_END_NODE&lt;/code&gt;：离开节点。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FDT_PROP&lt;/code&gt;：属性，包含长度、属性名在 strings block 中的偏移和属性值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FDT_NOP&lt;/code&gt;：保留的空操作 token。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FDT_END&lt;/code&gt;：structure block 结束。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;属性名集中存放在 strings block，&lt;code&gt;FDT_PROP&lt;/code&gt; 用偏移引用它们。节点和属性数据按 4 字节边界对齐；memory reservation entry 使用 64 位地址和大小。这里的数字和布局规则来自 FDT 结构规范及其头部定义，不应该简化成”固定 40 字节所以永远如此”。&lt;/p&gt;&lt;p&gt;我第一次看到这套布局时，感觉它像一个没有指针的压缩内存快照：解析器带着 offset 在几个区块之间走，遇到 begin/end token 用栈恢复树。这个设计牺牲了一点随机访问的舒服感，却换来了可复制、可重定位和早期启动阶段可解析的格式，算是很合理的嵌入式取舍。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;下一次动手做什么&lt;a href=&quot;#下一次动手做什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果你已经能读懂上面的最小例子，下一步不要继续收藏属性清单。找一块真实开发板，按下面的路径走一遍：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;找到它的板级 &lt;code&gt;.dts&lt;/code&gt; 和 SoC &lt;code&gt;.dtsi&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;沿着节点路径确认父节点的 &lt;code&gt;#address-cells&lt;/code&gt;、&lt;code&gt;#size-cells&lt;/code&gt; 和 &lt;code&gt;ranges&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在内核 &lt;code&gt;Documentation/devicetree/bindings/&lt;/code&gt; 中找到设备对应的 YAML。&lt;/li&gt;
&lt;li&gt;只修改一个 &lt;code&gt;status&lt;/code&gt; 或一个资源属性，重新编译 DTB。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;dtc&lt;/code&gt; 反编译产物，确认修改确实进入 DTB。&lt;/li&gt;
&lt;li&gt;启动 Linux，从 &lt;code&gt;/sys/firmware/devicetree/base&lt;/code&gt; 和 &lt;code&gt;dmesg&lt;/code&gt; 确认内核实际拿到的内容。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;未来同一份硬件描述继续被不同 client program 使用并不奇怪，但这不意味着设备树自动带来跨平台兼容。真正可移植的是清晰的描述接口；地址、时钟、中断和 binding 仍然属于具体硬件。至于我自己，下一步大概会挑一个真实 GPIO binding 做实验。先把一盏灯点亮，再考虑宇宙级抽象，yolo。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://devicetree-specification.readthedocs.io/en/stable/intro.html&quot; target=&quot;_blank&quot;&gt;Devicetree Specification: Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devicetree-specification.readthedocs.io/en/stable/devicetree-basics.html&quot; target=&quot;_blank&quot;&gt;Devicetree Specification: The Devicetree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devicetree-specification.readthedocs.io/en/stable/flattened-format.html&quot; target=&quot;_blank&quot;&gt;Devicetree Specification: Flattened Devicetree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devicetree-specification.readthedocs.io/en/stable/devicetree-source.html&quot; target=&quot;_blank&quot;&gt;Devicetree Specification: Devicetree Source Format&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.kernel.org/devicetree/usage-model.html&quot; target=&quot;_blank&quot;&gt;Linux Kernel Documentation: Linux and the Devicetree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.kernel.org/devicetree/bindings/index.html&quot; target=&quot;_blank&quot;&gt;Linux Kernel Documentation: Devicetree Bindings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git.kernel.org/pub/scm/utils/dtc/dtc.git/&quot; target=&quot;_blank&quot;&gt;Device Tree Compiler 官方仓库&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.kernel.org/devicetree/&quot; target=&quot;_blank&quot;&gt;Linux 内核 dtc 文档入口&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;</content:encoded></item><item><title>BibTeX 与 BibLaTeX 入门：.bib 文件结构、引用与编译流程</title><link>https://bfmhno3.github.io/posts/bibtex-and-biblatex-guide/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/bibtex-and-biblatex-guide/</guid><description>从 .bib 条目结构出发，结合 BibTeX 与 BibLaTeX 两种工作流，梳理引用写法、编译顺序和常见注意事项。</description><pubDate>Tue, 19 May 2026 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近恰逢本科阶段的倒数第二个课程设计，初步估计又得三四十页，并且老师不发模板只发排版要求，美其名曰让我们提前适应毕业论文写作（但是我记得毕业论文也是有 Word 模板的吧😂）。&lt;/p&gt;
&lt;p&gt;我并非是某一方的狂热爱好者，在我看来两者都是工具，只要能解决我的问题，那就是好工具。显然这种比较专业的排版需求还是使用 LaTeX 比较合适，而我发现我对 LaTeX 的引用数据库还是不太了解，就大概学习了一并下整理了这篇博客，不一定足够专业，但是应该能够应付我的课程设计了。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;1. &lt;code&gt;.bib&lt;/code&gt; 文件的基本结构&lt;a href=&quot;#1-bib-文件的基本结构&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;.bib&lt;/code&gt; 文件可以理解成一个文献数据库，由多条文献条目组成。每条条目一般是下面这种形式：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@entrytype&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;citekey&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;field1&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;value1&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;field2&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;value2&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@entrytype&lt;/code&gt;：文献类型，例如 &lt;code&gt;@article&lt;/code&gt;、&lt;code&gt;@book&lt;/code&gt;、&lt;code&gt;@inproceedings&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;citekey&lt;/code&gt;：引用键（citation key），在正文中通过 &lt;code&gt;\cite{citekey}&lt;/code&gt; 调用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;field = {value}&lt;/code&gt;：字段和值，如 &lt;code&gt;author&lt;/code&gt;、&lt;code&gt;title&lt;/code&gt;、&lt;code&gt;year&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;section&gt;&lt;h3&gt;1.1 常见条目类型与常用字段&lt;a href=&quot;#11-常见条目类型与常用字段&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;条目类型&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;th&gt;常用字段&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;@article&lt;/code&gt;&lt;/td&gt;&lt;td&gt;期刊论文&lt;/td&gt;&lt;td&gt;&lt;code&gt;author&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;journal&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;, &lt;code&gt;volume&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;pages&lt;/code&gt;, &lt;code&gt;doi&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;@inproceedings&lt;/code&gt;&lt;/td&gt;&lt;td&gt;会议论文&lt;/td&gt;&lt;td&gt;&lt;code&gt;author&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;booktitle&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;, &lt;code&gt;pages&lt;/code&gt;, &lt;code&gt;organization&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;@book&lt;/code&gt;&lt;/td&gt;&lt;td&gt;书籍&lt;/td&gt;&lt;td&gt;&lt;code&gt;author&lt;/code&gt; 或 &lt;code&gt;editor&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;publisher&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;@phdthesis&lt;/code&gt; / &lt;code&gt;@mastersthesis&lt;/code&gt;&lt;/td&gt;&lt;td&gt;学位论文&lt;/td&gt;&lt;td&gt;&lt;code&gt;author&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;school&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;@misc&lt;/code&gt; / &lt;code&gt;@online&lt;/code&gt;（BibLaTeX）&lt;/td&gt;&lt;td&gt;网页、预印本&lt;/td&gt;&lt;td&gt;&lt;code&gt;author&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;, &lt;code&gt;howpublished&lt;/code&gt; 或 &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;note&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;不同条目在 BibTeX 与 BibLaTeX 中的必填字段会有差异，建议以所用样式文档为准。可以通过 &lt;code&gt;texdoc biblatex&lt;/code&gt; 或 &lt;code&gt;texdoc bibtex&lt;/code&gt; 查看官方说明。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2. 一个最小可用的 &lt;code&gt;.bib&lt;/code&gt; 示例&lt;a href=&quot;#2-一个最小可用的-bib-示例&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;假设你新建一个文件 &lt;code&gt;references.bib&lt;/code&gt;，内容如下：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@article&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;einstein1905&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;  = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Albert Einstein&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;   = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;On the Electrodynamics of Moving Bodies&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;journal&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Annalen der Physik&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;    = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;1905&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;volume&lt;/span&gt;&lt;span&gt;  = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;17&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;pages&lt;/span&gt;&lt;span&gt;   = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;891--921&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@book&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;knuth1984&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;    = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Donald E. Knuth&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;     = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;The {TeX}book&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;publisher&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Addison-Wesley&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;      = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;1984&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@inproceedings&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;he2016resnet&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;    = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Kaiming He and Xiangyu Zhang and Shaoqing Ren and Jian Sun&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;     = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Deep Residual Learning for Image Recognition&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;booktitle&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;      = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;2016&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;pages&lt;/span&gt;&lt;span&gt;     = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;770--778&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;几个常用写法：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多个作者用 &lt;code&gt;and&lt;/code&gt; 分隔&lt;/strong&gt;（BibTeX/BibLaTeX 会自动识别作者列表）：&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;author = {First Author and Second Author and Third Author},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;页码用 &lt;code&gt;--&lt;/code&gt; 而不是 &lt;code&gt;-&lt;/code&gt;&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pages = {770--778},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;大小写保护&lt;/strong&gt;：专有名词或缩写（如 &lt;code&gt;TeX&lt;/code&gt;、&lt;code&gt;CNN&lt;/code&gt;）可用 &lt;code&gt;{}&lt;/code&gt; 包起来，避免被样式改写：&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;title = {Deep Residual Learning for {CNN} Image Recognition},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;hr /&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;3. 在 LaTeX 里如何使用 .bib（两种主流工作流）&lt;a href=&quot;#3-在-latex-里如何使用-bib两种主流工作流&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;传统路线&lt;/strong&gt;：LaTeX + BibTeX&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;现代路线&lt;/strong&gt;：LaTeX + BibLaTeX + biber&lt;/li&gt;
&lt;/ol&gt;&lt;hr /&gt;&lt;section&gt;&lt;h3&gt;3.1 传统：LaTeX + BibTeX 工作流&lt;a href=&quot;#31-传统latex--bibtex-工作流&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;文档示例（&lt;code&gt;main.tex&lt;/code&gt;）：&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\documentclass&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;article&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;utf8&lt;/span&gt;&lt;span&gt;]{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;inputenc&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;% 使用 XeLaTeX/LuaLaTeX 时通常不需要&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\begin&lt;/span&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;这是正文中的一条引用 &lt;/span&gt;&lt;span&gt;\cite&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;einstein1905&lt;/span&gt;&lt;span&gt;}，再来一条 &lt;/span&gt;&lt;span&gt;\cite&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;knuth1984&lt;/span&gt;&lt;span&gt;}。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\bibliographystyle&lt;/span&gt;&lt;span&gt;{ieeetr}   &lt;/span&gt;&lt;span&gt;% 选择参考文献样式，如 plain, unsrt, alpha, ieeetr 等&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\bibliography&lt;/span&gt;&lt;span&gt;{references}    &lt;/span&gt;&lt;span&gt;% 不写 .bib 后缀&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\end&lt;/span&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;编译顺序（非常关键）：&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pdflatex main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bibtex main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pdflatex main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pdflatex main&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;很多编辑器可以自动完成这些步骤，但底层顺序仍然是这 4 步。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;特点&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：历史悠久、兼容性好，很多旧模板和投稿模板仍要求 BibTeX。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;：对 UTF-8、多语种姓名支持相对有限；&lt;code&gt;.bst&lt;/code&gt; 样式定制门槛较高；对 URL、DOI、在线资源的处理不如 BibLaTeX 灵活。&lt;/li&gt;
&lt;/ul&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.2 现代：BibLaTeX + biber 工作流&lt;a href=&quot;#32-现代biblatex--biber-工作流&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;文档示例（&lt;code&gt;main.tex&lt;/code&gt;）：&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\documentclass&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;article&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;utf8&lt;/span&gt;&lt;span&gt;]{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;inputenc&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;% 使用 XeLaTeX/LuaLaTeX 时可省略&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;backend=biber,style=authoryear,sorting=nyt&lt;/span&gt;&lt;span&gt;]{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;biblatex&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\addbibresource&lt;/span&gt;&lt;span&gt;{references.bib} &lt;/span&gt;&lt;span&gt;% 指定 .bib 文件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\begin&lt;/span&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;这是正文中的一条引用 &lt;/span&gt;&lt;span&gt;\parencite&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;einstein1905&lt;/span&gt;&lt;span&gt;}，&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;以及一条行文引用 &lt;/span&gt;&lt;span&gt;\textcite&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;knuth1984&lt;/span&gt;&lt;span&gt;}。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\printbibliography&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\end&lt;/span&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;**&lt;code&gt;backend=biber&lt;/code&gt;：**指定后端为 &lt;code&gt;biber&lt;/code&gt;（而不是 &lt;code&gt;bibtex&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;**&lt;code&gt;style=authoryear&lt;/code&gt;：**作者-年份格式（如 “Einstein 1905”），也可改为 &lt;code&gt;numeric&lt;/code&gt;、&lt;code&gt;ieee&lt;/code&gt; 等。&lt;/li&gt;
&lt;li&gt;**&lt;code&gt;sorting=nyt&lt;/code&gt;：**按 &lt;code&gt;name-year-title&lt;/code&gt; 排序。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;编译顺序：&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pdflatex main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;biber main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pdflatex main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pdflatex main&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;如果你用的是 XeLaTeX/LuaLaTeX，则把 &lt;code&gt;pdflatex&lt;/code&gt; 换成 &lt;code&gt;xelatex&lt;/code&gt; 或 &lt;code&gt;lualatex&lt;/code&gt; 即可。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;特点&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：对 UTF-8 与多语言支持更好；字段更丰富（如 &lt;code&gt;@online&lt;/code&gt;、&lt;code&gt;url&lt;/code&gt;、&lt;code&gt;doi&lt;/code&gt;、&lt;code&gt;eprint&lt;/code&gt;）；样式可通过 LaTeX 宏定制，可维护性更高；支持按章节、按类型分组等高级功能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;：某些期刊/会议模板仍明确要求 BibTeX。&lt;/li&gt;
&lt;/ul&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;4. 该如何选择？&lt;a href=&quot;#4-该如何选择&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Important&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;如果模板没有明确限制，通常可以优先考虑 &lt;code&gt;XeLaTeX/LuaLaTeX + BibLaTeX + biber&lt;/code&gt;。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr /&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;5. 写 &lt;code&gt;.bib&lt;/code&gt; 时的一些实用小技巧&lt;a href=&quot;#5-写-bib-时的一些实用小技巧&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优先从数据库导出 BibTeX&lt;/strong&gt;：例如 Google Scholar、arXiv、ACM、IEEE、Springer，可减少手写错误。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统一命名规则（citekey）&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;比如 &lt;code&gt;authorYearShortTitle&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vaswani2017attention&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;he2016resnet&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;knuth1984texbook&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;中文文献的处理&lt;/strong&gt;：在 BibLaTeX + biber + UTF-8 工作流中，可直接使用中文 &lt;code&gt;title&lt;/code&gt;、&lt;code&gt;author&lt;/code&gt;；若投稿要求英文参考文献，可将英文信息写入主要字段，中文信息放在 &lt;code&gt;note&lt;/code&gt; 或 &lt;code&gt;language&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意特殊字符转义&lt;/strong&gt;：&lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt;, &lt;code&gt;_&lt;/code&gt;, &lt;code&gt;#&lt;/code&gt; 等应转义为 &lt;code&gt;\&amp;amp;&lt;/code&gt;, &lt;code&gt;\%&lt;/code&gt;, &lt;code&gt;\_&lt;/code&gt;, &lt;code&gt;\#&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;hr /&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;6. 完整示例（BibLaTeX + biber）&lt;a href=&quot;#6-完整示例biblatex--biber&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;下面给出一套可直接实验的最小流程。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;6.1 新建 &lt;code&gt;references.bib&lt;/code&gt;&lt;a href=&quot;#61-新建-referencesbib&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@article&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;vaswani2017attention&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;  = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Ashish Vaswani and Noam Shazeer and Niki Parmar and Jakob Uszkoreit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;             &lt;/span&gt;&lt;/span&gt;&lt;span&gt;and Llion Jones and Aidan N. Gomez and Lukasz Kaiser and Illia Polosukhin&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;   = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Attention Is All You Need&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;journal&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Advances in Neural Information Processing Systems&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;volume&lt;/span&gt;&lt;span&gt;  = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;    = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;2017&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@book&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;knuth1984texbook&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;    = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Donald E. Knuth&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;     = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;The {TeX}book&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;publisher&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Addison-Wesley&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;      = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;1984&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;6.2 新建 &lt;code&gt;main.tex&lt;/code&gt;&lt;a href=&quot;#62-新建-maintex&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\documentclass&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;article&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;utf8&lt;/span&gt;&lt;span&gt;]{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;inputenc&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\usepackage&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;backend=biber,style=authoryear,sorting=nyt&lt;/span&gt;&lt;span&gt;]{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;biblatex&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\addbibresource&lt;/span&gt;&lt;span&gt;{references.bib}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\begin&lt;/span&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;这是对 Transformer 的引用 &lt;/span&gt;&lt;span&gt;\parencite&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;vaswani2017attention&lt;/span&gt;&lt;span&gt;}，&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;这是对 &lt;/span&gt;&lt;span&gt;\textcite&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;knuth1984texbook&lt;/span&gt;&lt;span&gt;} 的引用。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\printbibliography&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;\end&lt;/span&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;6.3 编译顺序&lt;a href=&quot;#63-编译顺序&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pdflatex main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;biber main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pdflatex main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pdflatex main&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;如果你用的是 XeLaTeX，就把 &lt;code&gt;pdflatex&lt;/code&gt; 换成 &lt;code&gt;xelatex&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>Tmux 使用笔记</title><link>https://bfmhno3.github.io/posts/note-of-tmux/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/note-of-tmux/</guid><description>理解 tmux 的 C/S 架构和 Session、Window、Pane 的三层模型，快速掌握日常工作中最常用的快捷键，提升远程开发效率。</description><pubDate>Sat, 25 Apr 2026 13:55:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;为什么我们需要 tmux？&lt;a href=&quot;#为什么我们需要-tmux&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;1. 终端的物理与逻辑基础&lt;a href=&quot;#1-终端的物理与逻辑基础&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在计算机世界里，我们通过 “终端（Terminal）” 与 “Shell（如 bash, zsh）” 进行交互。&lt;/p&gt;&lt;p&gt;当你打开一个终端窗口，或者通过 SSH 连接到远程服务器时，操作系统为你分配了一个 &lt;strong&gt;TTY（伪终端）&lt;/strong&gt;，并在这个 TTY 上启动了一个 Shell 进程。你在终端里运行的所有程序（跑脚本、开服务器、编辑代码），都是这个 Shell 的&lt;strong&gt;子进程&lt;/strong&gt;。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. 致命的耦合（痛点所在）&lt;a href=&quot;#2-致命的耦合痛点所在&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这就产生了一个致命的 “生命周期耦合” 问题：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;规则&lt;/strong&gt;：当你的终端窗口关闭，或者由于网络波动导致 SSH 连接断开时，系统会向该 TTY 上的 Shell 发送一个 &lt;code&gt;SIGHUP&lt;/code&gt;（挂断信号）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：Shell 收到信号后会立即终止，并带着它所有的子进程一起退出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;痛点&lt;/strong&gt;：如果你正在服务器上训练一个需要跑 3 天的深度学习模型，或者正在编译一个巨大的项目，只要你的网络断开一秒钟，或者你不小心关掉了窗口，前功尽弃。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3. 需求推演&lt;a href=&quot;#3-需求推演&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;为了解决这个问题，我们需要在 “显示界面（你的屏幕/SSH连接）” 和 “后台进程（Shell 及你的任务）” 之间，插入一个&lt;strong&gt;中间层&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;这个中间层需要具备两个核心能力：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;进程解耦（持久化）&lt;/strong&gt;：它可以接管所有的 Shell 进程，并且无视网络断开和窗口关闭，永远在后台运行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多路复用（分屏）&lt;/strong&gt;：既然它接管了终端，那它最好能把一个屏幕切分成多个区域，让我们可以同时看代码、看日志、跑服务，而不需要开一堆 SSH 连接。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;这就是 &lt;strong&gt;tmux&lt;/strong&gt; 诞生的原因。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;tmux 是什么？（核心架构）&lt;a href=&quot;#tmux-是什么核心架构&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;tmux (Terminal Multiplexer，终端复用器)&lt;/strong&gt; 就是我们刚刚推导出的那个“中间层”。&lt;/p&gt;&lt;p&gt;它的底层本质是一个 &lt;strong&gt;C/S 架构（Client-Server）&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Server（服务端）&lt;/strong&gt;：当你第一次运行 tmux 时，它会在后台默默启动一个 Server。这个 Server 负责管理你所有的运行环境。只要服务器不重启，它就一直活着。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client（客户端）&lt;/strong&gt;：当你在终端敲下 &lt;code&gt;tmux&lt;/code&gt; 时，你实际上是启动了一个 Client，连接到了后台的 Server。把 Client 关掉，只是断开了 “显示器”，后台的 Server 和进程依旧在运转。&lt;/li&gt;
&lt;/ul&gt;&lt;section&gt;&lt;h3&gt;tmux 的层级逻辑（非常重要）&lt;a href=&quot;#tmux-的层级逻辑非常重要&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;为了方便管理，tmux 将你的工作区抽象为三个层级：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Session（会话）&lt;/strong&gt;：最大的单位。一个 Session 通常代表一个 “项目”。比如你可以建一个名叫 &lt;code&gt;work&lt;/code&gt; 的 Session，里面跑你的代码；再建一个名叫 &lt;code&gt;download&lt;/code&gt; 的 Session，里面专门用来挂机下载。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Window（窗口）&lt;/strong&gt;：Session 里面的 “标签页”。类似于浏览器的 Tab，一个屏幕只能显示一个 Window。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pane（窗格）&lt;/strong&gt;：Window 里的 “分屏”。你可以把一个 Window 切分成左边看代码、右边看日志。&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;🖥️ Tmux Server&lt;br /&gt;&lt;br /&gt;(后台进程，永不宕机)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;📁 Session: work&lt;br /&gt;&lt;br /&gt;(项目 1)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;📁 Session: download&lt;br /&gt;&lt;br /&gt;(项目 2)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;📑 Window 1&lt;br /&gt;&lt;br /&gt;(编辑代码)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;📑 Window 2&lt;br /&gt;&lt;br /&gt;(运行测试)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;📑 Window 3&lt;br /&gt;&lt;br /&gt;(下载管理)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;🖼️ Pane 1.1&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;🖼️ Pane 1.2&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;🖼️ Pane 1.3&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;🖼️ Pane 2.1&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;🖥️ Tmux Server&lt;br /&gt;&lt;br /&gt;(后台进程，永不宕机)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;📁 Session: work&lt;br /&gt;&lt;br /&gt;(项目 1)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;📁 Session: download&lt;br /&gt;&lt;br /&gt;(项目 2)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;📑 Window 1&lt;br /&gt;&lt;br /&gt;(编辑代码)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;📑 Window 2&lt;br /&gt;&lt;br /&gt;(运行测试)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;📑 Window 3&lt;br /&gt;&lt;br /&gt;(下载管理)&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;🖼️ Pane 1.1&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;🖼️ Pane 1.2&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;🖼️ Pane 1.3&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;🖼️ Pane 2.1&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;tmux 的使用方法&lt;a href=&quot;#tmux-的使用方法&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;理解了上面的逻辑，使用 tmux 就只是死记硬背几个命令而已了。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;1. 前置概念：Prefix Key（前缀键）&lt;a href=&quot;#1-前置概念prefix-key前缀键&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在 tmux 中，所有的快捷键都必须配合一个&lt;strong&gt;前缀键&lt;/strong&gt;使用，默认是 &lt;code&gt;Ctrl + b&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;操作逻辑：&lt;/strong&gt; 先同时按下 &lt;code&gt;Ctrl&lt;/code&gt; 和 &lt;code&gt;b&lt;/code&gt;，&lt;strong&gt;松开双手&lt;/strong&gt;，然后再按下一个键。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;em&gt;(下文中我将 &lt;code&gt;Ctrl + b&lt;/code&gt; 简称为 &lt;code&gt;Prefix&lt;/code&gt;)&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. Session 管理（在普通的 Linux 终端打字）&lt;a href=&quot;#2-session-管理在普通的-linux-终端打字&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是你和 Server 交互的命令：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;新建会话&lt;/strong&gt;：&lt;code&gt;tmux new -s myproject&lt;/code&gt; （新建一个名为 myproject 的会话并进入）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;脱离会话（后台运行）：&lt;/strong&gt; &lt;code&gt;Prefix&lt;/code&gt; 然后按 &lt;code&gt;d&lt;/code&gt; (detach)。你的界面会退回普通终端，但里面的程序还在跑！&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查看现有会话&lt;/strong&gt;：&lt;code&gt;tmux ls&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重新连接会话&lt;/strong&gt;：&lt;code&gt;tmux attach -t myproject&lt;/code&gt; (attach)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;彻底杀死会话&lt;/strong&gt;：&lt;code&gt;tmux kill-session -t myproject&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3. Window 管理（在 tmux 内部使用快捷键）&lt;a href=&quot;#3-window-管理在-tmux-内部使用快捷键&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;新建窗口&lt;/strong&gt;：&lt;code&gt;Prefix&lt;/code&gt; 然后按 &lt;code&gt;c&lt;/code&gt; (create)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;切换窗口&lt;/strong&gt;：&lt;code&gt;Prefix&lt;/code&gt; 然后按 &lt;code&gt;n&lt;/code&gt; (next，下一个)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Prefix&lt;/code&gt; 然后按 &lt;code&gt;p&lt;/code&gt; (previous，上一个)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Prefix&lt;/code&gt; 然后按 &lt;code&gt;数字键&lt;/code&gt; (直接跳转到指定窗口)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关闭当前窗口&lt;/strong&gt;：&lt;code&gt;Prefix&lt;/code&gt; 然后按 &lt;code&gt;&amp;amp;&lt;/code&gt; (会弹出确认提示)&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4. Pane 管理（在 tmux 内部使用快捷键）&lt;a href=&quot;#4-pane-管理在-tmux-内部使用快捷键&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是日常写代码最常用的分屏功能：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;左右分屏&lt;/strong&gt;：&lt;code&gt;Prefix&lt;/code&gt; 然后按 &lt;code&gt;%&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上下分屏&lt;/strong&gt;：&lt;code&gt;Prefix&lt;/code&gt; 然后按 &lt;code&gt;&quot;&lt;/code&gt; (双引号)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在分屏间移动光标&lt;/strong&gt;：&lt;code&gt;Prefix&lt;/code&gt; 然后按 &lt;code&gt;方向键&lt;/code&gt; (上下左右)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全屏/取消全屏当前分屏&lt;/strong&gt;：&lt;code&gt;Prefix&lt;/code&gt; 然后按 &lt;code&gt;z&lt;/code&gt; (zoom，非常实用，看长报错信息时按一下放大，看完再按一下缩小)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关闭当前分屏&lt;/strong&gt;：&lt;code&gt;Prefix&lt;/code&gt; 然后按 &lt;code&gt;x&lt;/code&gt; 或者直接输入 &lt;code&gt;exit&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;进阶：让 tmux 变得更好用&lt;a href=&quot;#进阶让-tmux-变得更好用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;可以参考开源项目 &lt;a href=&quot;https://github.com/gpakosz/.tmux&quot; target=&quot;_blank&quot;&gt;gpakosz/.tmux&lt;/a&gt;，这里仅列举出开源项目中使用的快捷键绑定。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;格式说明&lt;a href=&quot;#格式说明&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;prefix&amp;gt;&lt;/code&gt; 表示需要按下 &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;a&lt;/kbd&gt; 或者 &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;b&lt;/kbd&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; c&lt;/code&gt; 表示需要按下 &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;a&lt;/kbd&gt; 或者 &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;b&lt;/kbd&gt; 加上 &lt;kbd&gt;c&lt;/kbd&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; C-c&lt;/code&gt; 表示需要按下 &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;a&lt;/kbd&gt; 或者 &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;b&lt;/kbd&gt; 加上 &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;c&lt;/kbd&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;快捷键分类说明&lt;a href=&quot;#快捷键分类说明&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;下表按&lt;strong&gt;使用频率和优先级&lt;/strong&gt;分组，建议按顺序学习：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;📌 必学&lt;/strong&gt;：日常工作最核心的 5 个操作，学会了就能满足基本需求&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;⭐ 常用&lt;/strong&gt;：提升工作效率的 15 个快捷键，逐步熟悉即可&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;🔧 进阶&lt;/strong&gt;：高级功能和特殊场景，按需学习&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;快捷键速查表&lt;a href=&quot;#快捷键速查表&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;section&gt;&lt;h4&gt;📌 会话与窗口基础&lt;a href=&quot;#-会话与窗口基础&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;快捷键&lt;/th&gt;&lt;th&gt;功能&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;tmux new -s myproject&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;新建会话&lt;/strong&gt; myproject 并进入（普通终端执行）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;tmux ls&lt;/code&gt;&lt;/td&gt;&lt;td&gt;列出所有会话（普通终端执行）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;tmux attach -t myproject&lt;/code&gt;&lt;/td&gt;&lt;td&gt;重新连接会话（普通终端执行）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; d&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;脱离会话&lt;/strong&gt;，回到普通终端，后台保持运行&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; c&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;新建窗口&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; n&lt;/code&gt;&lt;/td&gt;&lt;td&gt;切换到 &lt;strong&gt;下一个&lt;/strong&gt; 窗口&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; p&lt;/code&gt;&lt;/td&gt;&lt;td&gt;切换到 &lt;strong&gt;上一个&lt;/strong&gt; 窗口&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; 数字&lt;/code&gt;&lt;/td&gt;&lt;td&gt;直接切换到指定窗口&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;📌 分屏基础&lt;a href=&quot;#-分屏基础&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;快捷键&lt;/th&gt;&lt;th&gt;功能&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; %&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;左右分屏&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; &quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;上下分屏&lt;/strong&gt;（双引号）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; 方向键&lt;/code&gt;&lt;/td&gt;&lt;td&gt;在分屏间 &lt;strong&gt;移动光标&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; z&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;放大/缩小&lt;/strong&gt; 当前分屏&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; x&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;关闭&lt;/strong&gt; 当前分屏&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;⭐ 常用操作&lt;a href=&quot;#-常用操作&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;快捷键&lt;/th&gt;&lt;th&gt;功能&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; e&lt;/code&gt;&lt;/td&gt;&lt;td&gt;打开 .local 配置文件（使用 &lt;span&gt;&lt;span&gt;VISUAL或VISUAL 或 &lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;V&lt;/span&gt;&lt;span&gt;I&lt;/span&gt;&lt;span&gt;S&lt;/span&gt;&lt;span&gt;U&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;L&lt;/span&gt;&lt;span&gt;或&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;EDITOR 变量定义的编辑器，默认 vim）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; r&lt;/code&gt;&lt;/td&gt;&lt;td&gt;重新加载配置&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; C-c&lt;/code&gt;&lt;/td&gt;&lt;td&gt;新建会话&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; C-f&lt;/code&gt;&lt;/td&gt;&lt;td&gt;按名称切换会话&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;⭐ 窗口与分屏导航&lt;a href=&quot;#-窗口与分屏导航&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;












&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;快捷键&lt;/th&gt;&lt;th&gt;功能&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;C-l&lt;/code&gt;&lt;/td&gt;&lt;td&gt;清空屏幕和 tmux 历史记录&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;快捷键&lt;/th&gt;&lt;th&gt;功能&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; C-h&lt;/code&gt;&lt;/td&gt;&lt;td&gt;导航到上一个窗口&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; C-l&lt;/code&gt;&lt;/td&gt;&lt;td&gt;导航到下一个窗口&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; Tab&lt;/code&gt;&lt;/td&gt;&lt;td&gt;切换到最后活跃的窗口&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;快捷键&lt;/th&gt;&lt;th&gt;功能&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; -&lt;/code&gt;&lt;/td&gt;&lt;td&gt;垂直分屏&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; _&lt;/code&gt;&lt;/td&gt;&lt;td&gt;水平分屏&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; h&lt;/code&gt;&lt;/td&gt;&lt;td&gt;向左导航窗格（Vim 风格）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; j&lt;/code&gt;&lt;/td&gt;&lt;td&gt;向下导航窗格（Vim 风格）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; k&lt;/code&gt;&lt;/td&gt;&lt;td&gt;向上导航窗格（Vim 风格）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; l&lt;/code&gt;&lt;/td&gt;&lt;td&gt;向右导航窗格（Vim 风格）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;🔧 进阶操作 - 窗格调整与交换&lt;a href=&quot;#-进阶操作---窗格调整与交换&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;




































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;快捷键&lt;/th&gt;&lt;th&gt;功能&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; H&lt;/code&gt;&lt;/td&gt;&lt;td&gt;向左调整窗格大小&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; J&lt;/code&gt;&lt;/td&gt;&lt;td&gt;向下调整窗格大小&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; K&lt;/code&gt;&lt;/td&gt;&lt;td&gt;向上调整窗格大小&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; L&lt;/code&gt;&lt;/td&gt;&lt;td&gt;向右调整窗格大小&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; &amp;lt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;与左边的窗格交换&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; &amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;与右边的窗格交换&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; +&lt;/code&gt;&lt;/td&gt;&lt;td&gt;最大化当前窗格到新窗口&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;🔧 进阶操作 - 鼠标与复制粘贴&lt;a href=&quot;#-进阶操作---鼠标与复制粘贴&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;快捷键&lt;/th&gt;&lt;th&gt;功能&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; m&lt;/code&gt;&lt;/td&gt;&lt;td&gt;切换鼠标模式（开启/关闭）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; Enter&lt;/code&gt;&lt;/td&gt;&lt;td&gt;进入复制模式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; b&lt;/code&gt;&lt;/td&gt;&lt;td&gt;查看粘贴缓冲区列表&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; p&lt;/code&gt;&lt;/td&gt;&lt;td&gt;从顶部粘贴缓冲区粘贴&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; P&lt;/code&gt;&lt;/td&gt;&lt;td&gt;选择粘贴缓冲区进行粘贴&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;🔧 进阶操作 - 工具集成&lt;a href=&quot;#-进阶操作---工具集成&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;快捷键&lt;/th&gt;&lt;th&gt;功能&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; U&lt;/code&gt;&lt;/td&gt;&lt;td&gt;启动 Urlscan（如果可用）或 Urlview&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;prefix&amp;gt; F&lt;/code&gt;&lt;/td&gt;&lt;td&gt;启动 Facebook PathPicker（如果可用）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;🔧 进阶操作 - Copy-mode-vi 绑定&lt;a href=&quot;#-进阶操作---copy-mode-vi-绑定&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;配置文件中的 &lt;code&gt;copy-mode-vi&lt;/code&gt; 设定与 Vim 配置相匹配，以下为复制模式下的快捷键：&lt;/p&gt;
































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;快捷键&lt;/th&gt;&lt;th&gt;功能&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;v&lt;/code&gt;&lt;/td&gt;&lt;td&gt;开始选择 / 可视模式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;C-v&lt;/code&gt;&lt;/td&gt;&lt;td&gt;在块状可视模式和可视模式之间切换&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;H&lt;/code&gt;&lt;/td&gt;&lt;td&gt;跳转到行首&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;L&lt;/code&gt;&lt;/td&gt;&lt;td&gt;跳转到行尾&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;y&lt;/code&gt;&lt;/td&gt;&lt;td&gt;复制选择内容到顶部粘贴缓冲区&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Escape&lt;/code&gt;&lt;/td&gt;&lt;td&gt;取消当前操作&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;提示&lt;/strong&gt;：你也可以通过在 .local 自定义配置文件中设置 &lt;code&gt;tmux_conf_preserve_stock_bindings&lt;/code&gt; 变量为 true，来保留 tmux 的原始按键绑定。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;总结&lt;a href=&quot;#总结&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;掌握 tmux 的关键不在于背熟所有的快捷键，而是在脑海中建立起 &lt;strong&gt;Server -&amp;gt; Session -&amp;gt; Window -&amp;gt; Pane&lt;/strong&gt; 的空间模型，以及理解它&lt;strong&gt;将显示与进程解耦&lt;/strong&gt;的核心价值。习惯它之后，你会发现它是服务器端开发的终极神器。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>别再死记 Linux 目录：从 FHS 到运行时文件系统</title><link>https://bfmhno3.github.io/posts/fhs-of-linux/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/fhs-of-linux/</guid><description>从 FHS 的两条分类轴出发，拆开持久文件、内核接口与进程视图，再用 5 组命令串起 usr-merge、XDG、namespace、cgroup、容器和嵌入式文件系统。</description><pubDate>Tue, 21 Apr 2026 07:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我以前理解 Linux 目录的方法，基本等价于背一张巨大的单词表：&lt;code&gt;/etc&lt;/code&gt; 放配置，&lt;code&gt;/var&lt;/code&gt; 放变化的数据，&lt;code&gt;/usr&lt;/code&gt; 放程序，背到 &lt;code&gt;/sys&lt;/code&gt;、&lt;code&gt;/proc&lt;/code&gt; 和 &lt;code&gt;/run&lt;/code&gt; 时，大脑就开始悄悄交换它们的位置。问题不在记忆力，而在模型错了。这次我换一种办法：先把目录树拆成几类性质完全不同的对象，再用几条命令观察一台正在运行的 Linux。&lt;/p&gt;
&lt;p&gt;FHS 3.0 发布于 2015 年 3 月 19 日。到 2026 年，它已经 11 岁了。它仍然是理解 Linux 文件布局的好起点，但它不是现代 Linux 根目录的完整地图，更不是内核接口、容器存储和桌面应用数据的总规范。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先建立一个能用的模型&lt;a href=&quot;#先建立一个能用的模型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;根目录 &lt;code&gt;/&lt;/code&gt; 看起来是一棵树，树枝却不一定来自同一块磁盘。它更像一张总配线架：Ext4、Btrfs、tmpfs、procfs、sysfs 和设备节点都接入同一个路径空间，我们恰好可以用普通的路径去访问它们。&lt;/p&gt;&lt;p&gt;我把这棵树分成三层：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;持久文件的放置约定&lt;/strong&gt;：FHS 主要回答软件、配置、日志和用户数据应该放在哪里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内核与运行时接口&lt;/strong&gt;：&lt;code&gt;/proc&lt;/code&gt;、&lt;code&gt;/sys&lt;/code&gt;、&lt;code&gt;/dev&lt;/code&gt;、&lt;code&gt;/run&lt;/code&gt; 往往由内核或早期用户空间在启动时提供。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进程看到的文件系统视图&lt;/strong&gt;：挂载命名空间和容器可以让两个进程看到不同的 &lt;code&gt;/&lt;/code&gt;，即使它们共享同一个内核。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;把这三层混在一起，就会得到一篇目录百科；每一项似乎都对，合起来却没有因果关系。下面逐层拆。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;FHS 到底解决什么问题？&lt;a href=&quot;#fhs-到底解决什么问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;FHS 不是为了让根目录看起来整齐。它要让软件、发行版、管理员和脚本能够预测文件的位置。标准用两条彼此独立的轴来组织数据：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;静态 (static) / 可变 (variable)&lt;/strong&gt;：文件是否会在没有管理员干预时变化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可共享 (shareable) / 不可共享 (unshareable)&lt;/strong&gt;：同一份文件能否被多台主机使用。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;FHS 3.0 自己给出的例子很干净：&lt;/p&gt;



















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;可共享&lt;/th&gt;&lt;th&gt;不可共享&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;静态&lt;/td&gt;&lt;td&gt;&lt;code&gt;/usr&lt;/code&gt;、&lt;code&gt;/opt&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;/etc&lt;/code&gt;、&lt;code&gt;/boot&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;可变&lt;/td&gt;&lt;td&gt;&lt;code&gt;/var/mail&lt;/code&gt;、&lt;code&gt;/var/spool/news&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;/var/run&lt;/code&gt;、&lt;code&gt;/var/lock&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这张 &lt;code&gt;2 x 2&lt;/code&gt; 表是设计原则，不是把每个目录硬塞进一个格子的分类游戏。例如 &lt;code&gt;/home&lt;/code&gt; 是否跨主机共享取决于部署方式，&lt;code&gt;/srv&lt;/code&gt; 的内容也由站点自己组织。模型负责解释边界，不负责替现实假装整齐。&lt;/p&gt;&lt;p&gt;静态和可变数据分开后，系统资源可以只读挂载，日志和队列可以采用不同的备份策略；可共享和本机专属数据分开后，多台机器可以复用同一份程序而保留各自配置。这才是目录划分真正带来的工程收益。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;根目录下的持久世界&lt;a href=&quot;#根目录下的持久世界&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;FHS 3.0 要求根目录包含 14 个目录或指向它们的符号链接，并在对应子系统存在时再提供 3 个可选目录。没必要逐字背诵，我更愿意沿着文件的生命周期来理解它们。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;/usr&lt;/code&gt;：发行版提供的静态系统资源&lt;a href=&quot;#usr发行版提供的静态系统资源&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;/usr&lt;/code&gt; 是主要的只读、可共享层。发行版或包管理器提供的大部分程序和静态资源都放在这里。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;code&gt;/usr&lt;/code&gt; 到底是什么缩写？&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;网上经常把 &lt;code&gt;/usr&lt;/code&gt; 解释为 “Universal System Resources”，但这不是 FHS 给出的官方全称，而是后来流传开的一种解释。FHS 3.0 并没有规定 &lt;code&gt;/usr&lt;/code&gt; 应该展开成哪几个英文单词，它只定义这个目录的用途。因此，与其记忆一个有争议的全称，不如直接记住它的目录语义：&lt;code&gt;/usr&lt;/code&gt; 主要保存通常可以只读挂载和跨主机共享的系统资源。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/usr/bin&lt;/code&gt;：适合从命令行调用的程序。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/lib&lt;/code&gt;：库、包的私有数据，以及不打算让用户直接执行的内部程序。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/include&lt;/code&gt;：面向用户空间开发的头文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/share&lt;/code&gt;：与 CPU 架构无关的数据，例如手册、时区和 locale 数据。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/usr/local&lt;/code&gt;：系统管理员在本机安装的软件层，避免与发行版管理的 &lt;code&gt;/usr&lt;/code&gt; 文件发生冲突。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这里有一个容易踩的坑：&lt;code&gt;/usr/include/linux&lt;/code&gt; 是提供给用户空间使用的 UAPI 头文件，不应该随手链接到任意内核源码树。编译外部内核模块时，常见入口是 &lt;code&gt;/lib/modules/$(uname -r)/build&lt;/code&gt;；它通常再指向发行版为当前内核准备的构建目录。用户空间 ABI 和内核模块构建环境是两件事。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;/etc&lt;/code&gt;：本机配置，而不是程序仓库&lt;a href=&quot;#etc本机配置而不是程序仓库&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;/etc&lt;/code&gt; 保存 host-specific system configuration，也就是本机系统配置。主机名、服务配置、账号数据库和网络策略通常落在这里。&lt;/p&gt;&lt;p&gt;FHS 明确要求 &lt;code&gt;/etc&lt;/code&gt; 不放二进制程序。现代系统还经常把发行版默认值放在 &lt;code&gt;/usr&lt;/code&gt;，只把管理员覆盖的配置放在 &lt;code&gt;/etc&lt;/code&gt;。于是配置的含义变得很清楚：&lt;code&gt;/usr&lt;/code&gt; 是供应方给出的默认状态，&lt;code&gt;/etc&lt;/code&gt; 是这台机器做出的决定。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;/var&lt;/code&gt;：需要跨重启保留的变化&lt;a href=&quot;#var需要跨重启保留的变化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;/var&lt;/code&gt; 保存运行中会变化、而且通常要持久化的数据：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/var/log&lt;/code&gt;：日志。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/var/lib&lt;/code&gt;：应用持久状态，例如数据库、包管理器状态和 &lt;code&gt;cloud-init&lt;/code&gt; 实例数据。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/var/cache&lt;/code&gt;：可以重新生成的缓存。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/var/spool&lt;/code&gt;：等待处理的队列，例如邮件和打印任务。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/var/tmp&lt;/code&gt;：预期可以跨重启保留的临时文件。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;“会变大” 不是判断 &lt;code&gt;/var&lt;/code&gt; 的充分条件。配置文件也会变化，但它属于 &lt;code&gt;/etc&lt;/code&gt;；用户文档也会变化，但它属于用户数据。关键问题是：这是不是系统或服务在运行中产生并需要保留的状态？&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;/run&lt;/code&gt;：只服务于本次启动&lt;a href=&quot;#run只服务于本次启动&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;/run&lt;/code&gt; 保存从本次启动开始才有意义的运行时数据，例如 PID 文件、Unix socket、锁和服务的瞬时状态。现代系统通常把它挂载为 &lt;code&gt;tmpfs&lt;/code&gt;，所以重启后自然消失。早期放在 &lt;code&gt;/var/run&lt;/code&gt; 和 &lt;code&gt;/var/lock&lt;/code&gt; 的内容，如今通常通过兼容链接指向 &lt;code&gt;/run&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;code&gt;/run&lt;/code&gt; 与 &lt;code&gt;/var&lt;/code&gt; 的分界不是 “内存对磁盘”，而是生命周期：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;重启后仍有意义，放 &lt;code&gt;/var&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;只描述当前这次启动，放 &lt;code&gt;/run&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这条规则比死记 “PID 文件放哪里” 有用得多。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;用户、临时文件与服务数据&lt;a href=&quot;#用户临时文件与服务数据&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/home&lt;/code&gt;：普通用户的主目录。程序应通过 &lt;code&gt;$HOME&lt;/code&gt; 或用户数据库查找它，不要假定一定是 &lt;code&gt;/home/&amp;lt;name&amp;gt;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/root&lt;/code&gt;：root 的主目录放在 &lt;code&gt;/home&lt;/code&gt; 外，使 &lt;code&gt;/home&lt;/code&gt; 尚未挂载时管理员仍可能登录修复。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/tmp&lt;/code&gt;：短生命周期临时文件，可能在启动时或定期清理。多用户环境中必须用 &lt;code&gt;mkstemp(3)&lt;/code&gt;、&lt;code&gt;mkdtemp(3)&lt;/code&gt; 等安全接口创建文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/srv&lt;/code&gt;：本机对外提供的服务数据，例如管理员维护的网站内容。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/opt&lt;/code&gt;：附加软件包的独立目录。它仍在 FHS 中，但现代发行版是否使用它取决于打包策略。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/boot&lt;/code&gt;：内核、initramfs 和引导加载器需要的静态文件。EFI 系统分区可能挂载在 &lt;code&gt;/boot&lt;/code&gt;、&lt;code&gt;/boot/efi&lt;/code&gt; 或 &lt;code&gt;/efi&lt;/code&gt;，这是发行版和引导方案的选择，不是所有 Linux 都固定为 &lt;code&gt;/boot/efi&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;根目录下的运行时接口&lt;a href=&quot;#根目录下的运行时接口&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;现在转向另一类东西。&lt;code&gt;/dev&lt;/code&gt;、&lt;code&gt;/proc&lt;/code&gt; 和 &lt;code&gt;/sys&lt;/code&gt; 看起来也是目录，但它们主要不是用来 “存放普通文件” 的。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;/dev&lt;/code&gt;：设备访问入口&lt;a href=&quot;#dev设备访问入口&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;/dev&lt;/code&gt; 中的条目是设备节点。它们把文件描述符这套统一接口连接到内核驱动，但 “一切皆文件” 只是一条有用的近似，不是说设备节点等价于磁盘上的普通文件。&lt;/p&gt;&lt;p&gt;例如，读写块设备、终端和随机数设备的语义完全不同。有些设备主要通过 &lt;code&gt;read&lt;/code&gt;、&lt;code&gt;write&lt;/code&gt; 工作，有些依赖 &lt;code&gt;ioctl&lt;/code&gt;，还有些根本不允许普通用户访问。现代发行版通常由 &lt;code&gt;devtmpfs&lt;/code&gt; 和 &lt;code&gt;udev&lt;/code&gt; 动态维护这里的内容。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;/proc&lt;/code&gt;：进程视图与历史形成的内核接口&lt;a href=&quot;#proc进程视图与历史形成的内核接口&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;procfs&lt;/code&gt; 最初围绕进程信息设计，因此 &lt;code&gt;/proc/&amp;lt;pid&amp;gt;&lt;/code&gt; 暴露进程的状态、文件描述符、内存映射和命名空间。后来许多全局内核接口也进入 &lt;code&gt;/proc&lt;/code&gt;，例如 &lt;code&gt;/proc/cpuinfo&lt;/code&gt;、&lt;code&gt;/proc/meminfo&lt;/code&gt; 和 &lt;code&gt;/proc/sys&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;它不是 “硬盘里恰好有很多零字节文件”。读取这些路径时，内核动态生成内容；写入某些路径时，内核把数据解释为控制请求。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;/sys&lt;/code&gt;：按内核对象组织的设备模型&lt;a href=&quot;#sys按内核对象组织的设备模型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;sysfs&lt;/code&gt; 导出内核对象、属性以及对象之间的关系。设备、驱动、总线、类和固件信息因此有了比 &lt;code&gt;/proc&lt;/code&gt; 更结构化的位置。&lt;/p&gt;&lt;p&gt;几个常见入口：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/sys/class&lt;/code&gt;：按设备类别组织的视图，例如网络接口、LED 和 hwmon。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/sys/bus&lt;/code&gt;：按总线组织设备与驱动。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/sys/devices&lt;/code&gt;：设备拓扑。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/sys/firmware/devicetree/base&lt;/code&gt;：设备树系统上，内核展开后的设备树视图。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/sys/fs&lt;/code&gt;：cgroup、BPF、pstore 等文件系统或内核子系统的挂载入口。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;code&gt;sysfs&lt;/code&gt; 属性通常是稳定 ABI 的一部分，但并不意味着所有子目录都适合应用程序随意写入。例如旧的 sysfs GPIO 用户接口已经被内核文档标为废弃，新程序应优先使用 GPIO 字符设备接口。能 &lt;code&gt;echo 1&lt;/code&gt; 不代表应该永远 &lt;code&gt;echo 1&lt;/code&gt;，这是 Shell 最容易制造的乐观主义。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;用 5 条命令观察真实系统&lt;a href=&quot;#用-5-条命令观察真实系统&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;到这里，目录名已经不再是一组定义。我们可以直接问正在运行的系统。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;1. 这个路径来自什么文件系统？&lt;a href=&quot;#1-这个路径来自什么文件系统&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;findmnt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-T&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;findmnt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-T&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/proc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;findmnt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-T&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/sys&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;findmnt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-T&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;关注 &lt;code&gt;SOURCE&lt;/code&gt;、&lt;code&gt;FSTYPE&lt;/code&gt; 和 &lt;code&gt;OPTIONS&lt;/code&gt;。一台常见系统可能分别显示磁盘文件系统、&lt;code&gt;proc&lt;/code&gt;、&lt;code&gt;sysfs&lt;/code&gt; 和 &lt;code&gt;tmpfs&lt;/code&gt;，但容器或不可变发行版会给出不同答案。&lt;code&gt;findmnt&lt;/code&gt; 的输出比凭目录名猜测可靠。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. &lt;code&gt;/bin&lt;/code&gt; 还是真目录吗？&lt;a href=&quot;#2-bin-还是真目录吗&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;readlink&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/bin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;readlink&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/sbin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;readlink&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/lib&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这种布局称为 &lt;strong&gt;usr-merge（&lt;code&gt;/usr&lt;/code&gt; 合并）&lt;/strong&gt;：原本分散在根目录和 &lt;code&gt;/usr&lt;/code&gt; 下的同类目录被合并到一起，文件统一放进 &lt;code&gt;/usr/bin&lt;/code&gt;、&lt;code&gt;/usr/sbin&lt;/code&gt;、&lt;code&gt;/usr/lib&lt;/code&gt;，再让 &lt;code&gt;/bin&lt;/code&gt;、&lt;code&gt;/sbin&lt;/code&gt;、&lt;code&gt;/lib&lt;/code&gt; 成为指向它们的符号链接。旧程序仍然可以访问 &lt;code&gt;/bin/ls&lt;/code&gt;，系统实际执行的却与 &lt;code&gt;/usr/bin/ls&lt;/code&gt; 是同一个文件。&lt;/p&gt;&lt;p&gt;早期系统把 &lt;code&gt;/bin&lt;/code&gt; 和 &lt;code&gt;/usr/bin&lt;/code&gt; 分开，是为了在 &lt;code&gt;/usr&lt;/code&gt; 分区尚未挂载时，仍能依靠根分区里的少量工具启动和修复系统。现代 Linux 通常先进入 initramfs，由它准备存储设备并挂载真正的根文件系统，旧边界的实际价值已经很小。usr-merge 因此不是删除兼容路径，而是用符号链接保留旧接口，同时让发行版只维护一套系统文件。&lt;/p&gt;&lt;p&gt;运行上面的 &lt;code&gt;readlink&lt;/code&gt; 命令，就能判断当前系统是否采用了这种布局。传统系统中的这些路径仍可能是彼此独立的目录。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3. 一个程序实际加载了哪些库？&lt;a href=&quot;#3-一个程序实际加载了哪些库&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ldd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/usr/bin/ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;输出会暴露动态加载器与架构相关库目录。在 Debian 的 Multiarch 系统上，你可能看到 &lt;code&gt;/lib/x86_64-linux-gnu&lt;/code&gt;；采用 multilib 的系统则常见 &lt;code&gt;/lib64&lt;/code&gt;。这是发行版 ABI 布局，不应该被简化成 “64 位系统一律使用 &lt;code&gt;/lib64&lt;/code&gt;”。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;不要对不可信的可执行文件运行 &lt;code&gt;ldd&lt;/code&gt;。部分实现可能执行目标程序。分析未知文件时应使用发行版推荐的静态检查工具。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4. 当前进程属于哪些命名空间？&lt;a href=&quot;#4-当前进程属于哪些命名空间&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;readlink&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/proc/self/ns/mnt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;readlink&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/proc/self/ns/net&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;readlink&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/proc/self/ns/pid&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;输出类似 &lt;code&gt;mnt:[4026531832]&lt;/code&gt;。两个进程对应条目的设备号和 inode 相同，才说明它们处在同一个该类型命名空间。方括号里的数字是 namespace inode，不是所谓的 “物理隔离宇宙哈希值”。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;5. cgroup v2 怎样表达资源边界？&lt;a href=&quot;#5-cgroup-v2-怎样表达资源边界&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;findmnt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-T&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/sys/fs/cgroup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/proc/self/cgroup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;现代 cgroup v2 通常呈现一棵统一层级。控制器通过 &lt;code&gt;cpu.max&lt;/code&gt;、&lt;code&gt;memory.max&lt;/code&gt; 等接口设置限制，进程通过 &lt;code&gt;cgroup.procs&lt;/code&gt; 加入 cgroup。Docker 和 Kubernetes 会使用这些内核机制，但 &lt;code&gt;/sys/fs/cgroup&lt;/code&gt; 本身不属于 FHS 的文件放置规则。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;开发程序时，文件应该落在哪里？&lt;a href=&quot;#开发程序时文件应该落在哪里&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果我在写一个系统服务，比背目录更实用的是按所有者和生命周期做决定。&lt;/p&gt;&lt;p&gt;FHS 主要安排系统级文件，但桌面和命令行程序还需要保存每个用户自己的配置、数据、缓存和运行状态。过去很多程序直接在 &lt;code&gt;$HOME&lt;/code&gt; 下创建 &lt;code&gt;.vim&lt;/code&gt;、&lt;code&gt;.mozilla&lt;/code&gt;、&lt;code&gt;.cache-of-something&lt;/code&gt; 等隐藏目录，久而久之，用户主目录就变成了应用程序各自圈地的现场。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;XDG Base Directory 规范&lt;/strong&gt;解决的就是这个问题。XDG 是 freedesktop.org 规范体系沿用的历史名称；这里真正需要记住的是 Base Directory：它不强迫所有程序使用几个写死的路径，而是通过一组环境变量，为不同生命周期的数据指定基础目录。程序优先读取环境变量，变量未设置时再使用规范给出的默认值。&lt;/p&gt;&lt;p&gt;例如，主题偏好属于配置，应放进 &lt;code&gt;$XDG_CONFIG_HOME&lt;/code&gt;；下载后可以重新生成的缩略图属于缓存，应放进 &lt;code&gt;$XDG_CACHE_HOME&lt;/code&gt;；Unix socket 只在当前登录会话中有效，应放进 &lt;code&gt;$XDG_RUNTIME_DIR&lt;/code&gt;。这样备份配置时不必带上缓存，退出登录时也不必保留 socket。目录分类最终对应的是数据生命周期，而不是单纯为了让 &lt;code&gt;$HOME&lt;/code&gt; 看起来漂亮。&lt;/p&gt;












































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;内容&lt;/th&gt;&lt;th&gt;系统级位置&lt;/th&gt;&lt;th&gt;用户级默认位置&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;可执行程序&lt;/td&gt;&lt;td&gt;发行版包用 &lt;code&gt;/usr/bin&lt;/code&gt;，本机安装常用 &lt;code&gt;/usr/local/bin&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;~/.local/bin&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;静态数据&lt;/td&gt;&lt;td&gt;&lt;code&gt;/usr/share/&amp;lt;app&amp;gt;&lt;/code&gt; 或 &lt;code&gt;/usr/local/share/&amp;lt;app&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;$XDG_DATA_HOME/&amp;lt;app&amp;gt;&lt;/code&gt;，默认 &lt;code&gt;~/.local/share/&amp;lt;app&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;管理员配置&lt;/td&gt;&lt;td&gt;&lt;code&gt;/etc/&amp;lt;app&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;$XDG_CONFIG_HOME/&amp;lt;app&amp;gt;&lt;/code&gt;，默认 &lt;code&gt;~/.config/&amp;lt;app&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;持久状态&lt;/td&gt;&lt;td&gt;&lt;code&gt;/var/lib/&amp;lt;app&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;$XDG_STATE_HOME/&amp;lt;app&amp;gt;&lt;/code&gt;，默认 &lt;code&gt;~/.local/state/&amp;lt;app&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;可重建缓存&lt;/td&gt;&lt;td&gt;&lt;code&gt;/var/cache/&amp;lt;app&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;$XDG_CACHE_HOME/&amp;lt;app&amp;gt;&lt;/code&gt;，默认 &lt;code&gt;~/.cache/&amp;lt;app&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;本次启动的 socket、PID&lt;/td&gt;&lt;td&gt;&lt;code&gt;/run/&amp;lt;app&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;$XDG_RUNTIME_DIR/&amp;lt;app&amp;gt;&lt;/code&gt;，常见于 &lt;code&gt;/run/user/&amp;lt;uid&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;日志&lt;/td&gt;&lt;td&gt;&lt;code&gt;/var/log/&amp;lt;app&amp;gt;&lt;/code&gt; 或日志服务&lt;/td&gt;&lt;td&gt;用户状态目录或日志服务&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;XDG Base Directory 规范定义的不是三个固定隐藏目录，而是一组环境变量和回退值。尤其容易漏掉的是 &lt;code&gt;$XDG_STATE_HOME&lt;/code&gt; 和 &lt;code&gt;$XDG_RUNTIME_DIR&lt;/code&gt;：前者保存跨重启但不值得当用户数据同步的状态，后者只在登录会话期间有效，权限必须是 &lt;code&gt;0700&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;如果程序支持命令行参数或环境变量覆盖路径，优先级也应明确。目录规范解决的是默认位置，不应该剥夺管理员部署软件的能力。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;容器为什么能看到另一棵 &lt;code&gt;/&lt;/code&gt;？&lt;a href=&quot;#容器为什么能看到另一棵-&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;容器没有发明一套新的 FHS。它组合了几种内核机制：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Mount namespace&lt;/strong&gt; 给进程一张独立的挂载表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OverlayFS&lt;/strong&gt; 把只读 lower layer 和可写 upper layer 合并成一个视图。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PID、network、UTS、IPC、user 等 namespace&lt;/strong&gt; 隔离其他全局资源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cgroup&lt;/strong&gt; 统计并限制一组进程的资源。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;因此，容器里的 &lt;code&gt;/etc&lt;/code&gt; 仍然表达该容器视图中的配置，&lt;code&gt;/var&lt;/code&gt; 仍然表达该视图中的可变状态。变化的是目录树背后的挂载来源和可见范围，不是路径语义凭空消失了。&lt;/p&gt;&lt;p&gt;&lt;code&gt;/proc/&amp;lt;pid&amp;gt;/ns&lt;/code&gt; 只是命名空间对象的句柄。命名空间由 &lt;code&gt;clone(2)&lt;/code&gt;、&lt;code&gt;unshare(2)&lt;/code&gt; 或 &lt;code&gt;setns(2)&lt;/code&gt; 等系统调用创建或加入，不是 Docker “创建了一套文件链接”。文件系统在这里提供观察与引用对象的接口，而不是机制本身。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;嵌入式系统为什么看起来不太一样？&lt;a href=&quot;#嵌入式系统为什么看起来不太一样&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;资源受限设备通常保留 FHS 的大致语义，却会改变底层存储方式。OpenWrt 常见的根文件系统就是一个好例子：只读 SquashFS 作为 lower layer，可写 JFFS2 或 UBIFS 作为 upper layer，再通过 overlay 组合成可写的 &lt;code&gt;/&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;这解释了两个看似矛盾的现象：用户可以修改 &lt;code&gt;/etc/config&lt;/code&gt;，恢复出厂设置又能回到固件中的初始状态。修改通常进入可写层，而不是改写只读固件镜像。&lt;/p&gt;&lt;p&gt;原始 NAND 或 NOR Flash 则由 MTD 子系统暴露。&lt;code&gt;/dev/mtdX&lt;/code&gt; 是字符设备接口，&lt;code&gt;/dev/mtdblockX&lt;/code&gt; 提供受限的块设备视图。后者并不会凭空提供完整的磨损均衡；需要处理坏块和磨损时，应使用适合原始 Flash 的 UBI/UBIFS 等层。把 &lt;code&gt;mtdblock&lt;/code&gt; 当成 “内核替我把 Flash 变成普通硬盘”，大概是数据最不希望听到的一句话。&lt;/p&gt;&lt;p&gt;设备树、MTD、OverlayFS 都是 Linux 机制，不属于 FHS。它们出现在同一棵目录树里，是因为 VFS 允许不同文件系统和内核对象共享统一的路径空间。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后保留这张判断表&lt;a href=&quot;#最后保留这张判断表&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;遇到陌生目录时，我现在依次问 5 个问题：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;它是普通持久文件，还是一个挂载出来的运行时接口？&lt;/li&gt;
&lt;li&gt;谁拥有它：发行版、管理员、服务、用户，还是内核？&lt;/li&gt;
&lt;li&gt;内容是静态资源、配置、持久状态、缓存，还是瞬时状态？&lt;/li&gt;
&lt;li&gt;它是否需要跨重启、跨用户或跨主机存在？&lt;/li&gt;
&lt;li&gt;当前进程看到的路径，是否受 mount namespace 或 overlay 影响？&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;这 5 个问题足以推导大多数目录的意义。FHS 提供第一层坐标，XDG 把同样的思想延伸到用户目录，内核伪文件系统暴露运行状态，namespace 和 VFS 再决定某个进程最终看见哪棵树。&lt;/p&gt;&lt;p&gt;再往下走，就不再是 “文件应该放哪里” 的问题了，而是 dentry、inode、mount、superblock 和内核对象如何把不同世界接到同一个 &lt;code&gt;/&lt;/code&gt; 上。到这里目录表终于变成了一套系统模型，而不是 30 个待背单词。很好，我的大脑可以下班了。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.html&quot; target=&quot;_blank&quot;&gt;Filesystem Hierarchy Standard 3.0&lt;/a&gt;，FHS 的正式规范与二维分类定义。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://uapi-group.org/specifications/specs/linux_file_system_hierarchy/&quot; target=&quot;_blank&quot;&gt;UAPI.9 Linux File System Hierarchy&lt;/a&gt;，面向现代 Linux 的目录布局说明，目前标记为 work in progress。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man7/hier.7.html&quot; target=&quot;_blank&quot;&gt;Linux manual page: hier(7)&lt;/a&gt;，典型 Linux 目录层次的手册页。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://systemd.io/THE_CASE_FOR_THE_USR_MERGE/&quot; target=&quot;_blank&quot;&gt;systemd: The Case for the &lt;code&gt;/usr&lt;/code&gt; Merge&lt;/a&gt;，usr-merge 的布局、兼容性和历史背景。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://specifications.freedesktop.org/basedir/latest/&quot; target=&quot;_blank&quot;&gt;XDG Base Directory Specification&lt;/a&gt;，用户配置、数据、状态、缓存与运行时目录规范。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.kernel.org/filesystems/proc.html&quot; target=&quot;_blank&quot;&gt;Linux Kernel Documentation: The &lt;code&gt;/proc&lt;/code&gt; Filesystem&lt;/a&gt;，procfs 的内核文档。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.kernel.org/filesystems/sysfs.html&quot; target=&quot;_blank&quot;&gt;Linux Kernel Documentation: sysfs&lt;/a&gt;，sysfs 与内核对象模型。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.kernel.org/admin-guide/cgroup-v2.html&quot; target=&quot;_blank&quot;&gt;Linux Kernel Documentation: Control Group v2&lt;/a&gt;，cgroup v2 层级、控制器与接口文件。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man7/namespaces.7.html&quot; target=&quot;_blank&quot;&gt;Linux manual page: namespaces(7)&lt;/a&gt;，Linux namespace 类型、系统调用与 &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/ns&lt;/code&gt; 接口。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.kernel.org/filesystems/overlayfs.html&quot; target=&quot;_blank&quot;&gt;Linux Kernel Documentation: Overlay Filesystem&lt;/a&gt;，OverlayFS 的 lower、upper、work 与 merged 视图。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.kernel.org/driver-api/mtd/index.html&quot; target=&quot;_blank&quot;&gt;Linux Kernel Documentation: Memory Technology Device&lt;/a&gt;，MTD 子系统与原始 Flash 设备接口。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;</content:encoded></item><item><title>英语语法综述</title><link>https://bfmhno3.github.io/posts/overview-of-english-grammar/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/overview-of-english-grammar/</guid><description>本文系统梳理了英语语法的主体框架，从最基本的“简单句和五大定基本型”入手，解析句子成分与词性的区别。文章深入剖析了英语语法的核心枢纽——动词，涵盖其分类、时态、语气及非谓语形式，帮助读者以全局视角清晰建立造句的底层逻辑。</description><pubDate>Mon, 06 Apr 2026 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我们为什么学语法？&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;当然是为了考高分啊😂&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;其实，所有的英语语法都只有一个目的：&lt;strong&gt;造句&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;英语和中文一样，我们说话写文章有的时候用短句子，有的时候用长句子。不同的短句子又可以组合成新的长句。但这里说的是句子的长度，并不适合语法讨论。&lt;/p&gt;
&lt;p&gt;我们只有把句子拆开来，直到拆到不能再继续拆了，否则句意会不完整的 “基本句”，才好研究语法。因为如果 “基本句” 研究透了，长句子就是它们的组合。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;1. 简单句（Simple Sentence）&lt;a href=&quot;#1-简单句simple-sentence&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我们把这样的不能再拆的 “基本句” 称作&lt;strong&gt;简单句&lt;/strong&gt;（Simple Sentence）。而这样的简单句，除去那些&lt;strong&gt;感叹句&lt;/strong&gt;（“嗯”，“哦”，“啊”等）、&lt;strong&gt;省略句&lt;/strong&gt;、&lt;strong&gt;（向整句提问的）问句&lt;/strong&gt;，几乎全部都是在说：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;什么&lt;/strong&gt; + &lt;strong&gt;怎么样&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;绝大多数句子粗略地分一共就只有这两个部分，而这个 “什么” 和 “怎么样” 分别对应了两个最基本的&lt;strong&gt;句子成分&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;“什么”： &lt;strong&gt;主语&lt;/strong&gt;（Subject）。&lt;/li&gt;
&lt;li&gt;“怎么样”：&lt;strong&gt;谓语&lt;/strong&gt;（Predicate）。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;换句话说，几乎所有的英语句子结构都是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;主语&lt;/strong&gt; + &lt;strong&gt;谓语&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这里的&lt;strong&gt;主语&lt;/strong&gt;一般是人或物，不管是抽象还是具体。而这里的&lt;strong&gt;谓语&lt;/strong&gt;则是指一个广义的动作或发生的事情。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;这个 “动作” 不是我们平时狭义说的要动起来的 “动作”，而这个广义的 “动作” 也就是我们语法上说的&lt;strong&gt;动词&lt;/strong&gt;（Verb）。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2. 谓语动词&lt;a href=&quot;#2-谓语动词&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;序号&lt;/th&gt;&lt;th&gt;动词类型&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;不及物动词&lt;/td&gt;&lt;td&gt;可以独立完成的动作&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;单及物动词&lt;/td&gt;&lt;td&gt;有一个动作的承受者&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;双及物动词&lt;/td&gt;&lt;td&gt;有两个动作承受者&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;复杂及物动词&lt;/td&gt;&lt;td&gt;只有一个动作承受者&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;系动词&lt;/td&gt;&lt;td&gt;非 “动作”&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;section&gt;&lt;h3&gt;2.1 不及物动词（Intransitive Verb）&lt;a href=&quot;#21-不及物动词intransitive-verb&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;不及物动词&lt;/strong&gt;（Intransitive Verb）就是指&lt;strong&gt;可以独立完成的动作&lt;/strong&gt;，例如：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Papa Rabbit sleeps.&lt;/p&gt;&lt;p&gt;兔老爹睡觉。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;类似 “sleeps” 这样的没有承受者的动词就是&lt;strong&gt;不及物动词&lt;/strong&gt;（Intransitive Verb）。&lt;/p&gt;&lt;p&gt;不及物动词所对应的基本句子结构就是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Tip&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;主语&lt;/strong&gt; + &lt;strong&gt;不及物动词&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.2 单及物动词（Monotransitive Verb）&lt;a href=&quot;#22-单及物动词monotransitive-verb&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;&lt;p&gt;Papa Rabbit likes you.&lt;/p&gt;&lt;p&gt;兔老爹喜欢你。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;如果这里只说 “兔老爹喜欢”，你肯定觉得意思不完整，因为像 “喜欢” 这样的动词没有承受者就没有什么实际意义，这样的动词就属于&lt;strong&gt;及物动词&lt;/strong&gt;（Transitive Verb），而这个动作承受者就是&lt;strong&gt;宾语&lt;/strong&gt;（Object）。&lt;/p&gt;&lt;p&gt;具体而言，对于只有一个宾语及物动词，我们称为&lt;strong&gt;单及物动词&lt;/strong&gt;（Monotransitive Verb）。&lt;/p&gt;&lt;p&gt;单及物动词对应的基本句子结构就是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Tip&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;主语&lt;/strong&gt; + &lt;strong&gt;单及物动词&lt;/strong&gt; + &lt;strong&gt;宾语&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.3 双及物动词（Ditransitive Verbs）&lt;a href=&quot;#23-双及物动词ditransitive-verbs&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;&lt;p&gt;Papa Rabbit teaches you English.&lt;/p&gt;&lt;p&gt;兔老爹教你英语。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这里的核心动词是 “teach”（教），但是教授的知识是 “英语”，而知识的接收对象是 “你”。在语法上，我们把这样的动词的两个承受者分别称为&lt;strong&gt;直接宾语&lt;/strong&gt;（Direct Object）和&lt;strong&gt;间接宾语&lt;/strong&gt;（Indirect Object）。&lt;/p&gt;&lt;p&gt;如果只说 “兔老爹教英语”，其实语义已经完整了，所以 “English” 这里就是&lt;strong&gt;直接宾语&lt;/strong&gt;，而如果只说 “兔老爹教你”，而又没有上下文的话，你肯定觉得还缺了什么，所以 “you” 在这里是间接宾语。&lt;/p&gt;&lt;p&gt;这样的往往既有直接宾语又有间接宾语的动词，也是及物动词的一种，更具体地说，它属于&lt;strong&gt;双及物动词&lt;/strong&gt;（Ditransitive Verbs）。&lt;/p&gt;&lt;p&gt;双及物动词对应的基本句子结构就是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Tip&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;主语&lt;/strong&gt; + &lt;strong&gt;双及物动词&lt;/strong&gt; + &lt;strong&gt;间接宾语&lt;/strong&gt; + &lt;strong&gt;直接宾语&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.4 复杂及物动词（Complex-transitive Verb）&lt;a href=&quot;#24-复杂及物动词complex-transitive-verb&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;&lt;p&gt;Papa Rabbit considers you smart.&lt;/p&gt;&lt;p&gt;兔老爹认为你聪明。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这里虽然只有一个动作的承受者 “you”，但如果只说 “兔老爹认为你”，你肯定会觉得话没有说完，但是宾语 “you” 后面的 “smart” 却也不像双及物动词那样是另一个动作承受者。&lt;/p&gt;&lt;p&gt;这样的动词，必须要有个补充承受者的信息才意义完整，而这个补充的信息，我们在语法上称为&lt;strong&gt;补足语&lt;/strong&gt;或&lt;strong&gt;补语&lt;/strong&gt;（Complement）。更明确也可以说是&lt;strong&gt;宾语补语&lt;/strong&gt;（Object Complement）。&lt;/p&gt;&lt;p&gt;这样的需要&lt;strong&gt;补语&lt;/strong&gt;的动词，我们称之为&lt;strong&gt;复杂及物动词&lt;/strong&gt;（Complex-transitive Verb）&lt;/p&gt;&lt;p&gt;复杂及物动词对应的基本句子结构就是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Tip&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;主语&lt;/strong&gt; + &lt;strong&gt;复杂及物动词&lt;/strong&gt; + &lt;strong&gt;宾语&lt;/strong&gt; + &lt;strong&gt;（宾语）补语&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.5 系动词（Linking Verb）&lt;a href=&quot;#25-系动词linking-verb&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这种动作所表示的含义与狭义上的 “动作” 不太一样：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Papa Rabbit is tall.&lt;/p&gt;&lt;p&gt;兔老爹是高的。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这里的 “is” 在中文里经常翻译成 “是”，但它实际上的作用很简单：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Caution&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;就是把这个动词之后的信息&lt;strong&gt;赋予&lt;/strong&gt;前面的主语。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这里的 “is” 说白了就是把 “Papa Rabbit” 和 “tall” 连在一起，划上等号而已。或者说，把后者信息赋予前者。如果把 “tall” 换成 “in the room”：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Papa Rabbit is in the room.&lt;/p&gt;&lt;p&gt;兔老爹在房间里。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;其实也是把 “in the room” 这个状态性质赋予 “Papa Rabbit” 而已。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Papa Rabbit looks tall.&lt;/p&gt;&lt;p&gt;兔老爹看起来高。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这个 “look” 通常意义是 “看”，但是这里到底是谁在看？其实不是 “Papa Rabbit” 在看，“Papa Rabbit” 在这里是&lt;strong&gt;被看&lt;/strong&gt;。这里的 “looks” 其实也是把后面的 “tall” 赋予前面的主语 “Papa Rabbit” 而已，也算是划等号，只不过这个划等号比之前的 “is” 多了个 “看上去”。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Papa Rabbit smells nice.&lt;/p&gt;&lt;p&gt;兔老爹闻上去香。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这里的 “smells” 还是把 “Papa Rabbit” 和 “nice” 划等号，只不过还有 “闻上去” 这层意思。&lt;/p&gt;&lt;p&gt;像这样的赋予主语某种性质状态的 “划等号” 的动词，我们称之为&lt;strong&gt;连系动词&lt;/strong&gt;（Linking Verb），也称为&lt;strong&gt;系动词&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;系动词后面的补充信息自然就是&lt;strong&gt;补足语&lt;/strong&gt;或&lt;strong&gt;补语&lt;/strong&gt;，更精确地说是&lt;strong&gt;主语补语&lt;/strong&gt;，而&lt;strong&gt;主语补语&lt;/strong&gt;在中国的英语教学中又被称为&lt;strong&gt;表语&lt;/strong&gt;（Predicative）。&lt;/p&gt;&lt;p&gt;系动词对应的基本句子结构就是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Tip&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;主语&lt;/strong&gt; + &lt;strong&gt;系动词&lt;/strong&gt; + &lt;strong&gt;（主语）补语/表语&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;3. 五大基本句型&lt;a href=&quot;#3-五大基本句型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;序号&lt;/th&gt;&lt;th&gt;句型&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;主语 + 不及物动词&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;主语 + 单及物动词 + 宾语&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;主语 + 双及物动词 + 间接宾语 + 直接宾语&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;主语 + 复杂及物动词 + 宾语 + 宾语补语&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;主语 + 系动词 + 主语补语/表语&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;还有一种 “八大句型” 的分类，即在 “五大句型” 基础上增加：&lt;/p&gt;&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;there be&lt;/strong&gt; 句型，如：There is a rabbit. 可理解为第 5 种句型 “主语 + 系动词 + 主语补语/表语” 的倒装。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;主语&lt;/strong&gt; + &lt;strong&gt;谓语&lt;/strong&gt; + &lt;strong&gt;状语&lt;/strong&gt;，如：I live in China. 可理解为第 1 种句型 “主语 + 不及物动词” 的延伸（虽然这里的状语很重要）。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;主语&lt;/strong&gt; + &lt;strong&gt;谓语动词&lt;/strong&gt; + &lt;strong&gt;宾语&lt;/strong&gt; + &lt;strong&gt;状语&lt;/strong&gt;，如：I put the carrot on the table. 可理解为第 4 种句型 “主语 + 复杂及物动词 + 宾语 + 宾语补语” 的延伸。&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;严格说来，谓语和谓语动词是有区别的。句子主语后面余下的部分就是谓语，谓语动词只是谓语的一部分。&lt;/p&gt;&lt;p&gt;但是在平时，通俗地，为了方便，很多人会直接称呼谓语动词为谓语，而把 “主语 + 谓语动词 + 宾语” 这样的句子结构直接称呼为 “主语 + 谓语 + 宾语”（或 “主谓宾”），这也不是什么大问题。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;4. 句子基本成分&lt;a href=&quot;#4-句子基本成分&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;



































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;序号&lt;/th&gt;&lt;th&gt;成分&lt;/th&gt;&lt;th&gt;英文名&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;例句&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;主语&lt;/td&gt;&lt;td&gt;Subject&lt;/td&gt;&lt;td&gt;动作的发出者或被讨论的对象&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Papa Rabbit&lt;/strong&gt; eats carrots.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;谓语动词&lt;/td&gt;&lt;td&gt;Predicate Verb&lt;/td&gt;&lt;td&gt;表示具体的动作或状态&lt;/td&gt;&lt;td&gt;Papa Rabbit &lt;strong&gt;eats&lt;/strong&gt; carrots.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;宾语&lt;/td&gt;&lt;td&gt;Object&lt;/td&gt;&lt;td&gt;动作的承受者&lt;/td&gt;&lt;td&gt;Papa Rabbit eats &lt;strong&gt;carrots&lt;/strong&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;宾语补语&lt;/td&gt;&lt;td&gt;Object Complement&lt;/td&gt;&lt;td&gt;补充说明宾语的状态或特征&lt;/td&gt;&lt;td&gt;Papa Rabbit considers you &lt;strong&gt;smart&lt;/strong&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;主语补语&lt;/td&gt;&lt;td&gt;Subject Complement&lt;/td&gt;&lt;td&gt;补充说明主语的性质状态（即表语）&lt;/td&gt;&lt;td&gt;Papa Rabbit is &lt;strong&gt;tall&lt;/strong&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;定语&lt;/td&gt;&lt;td&gt;Attributive&lt;/td&gt;&lt;td&gt;修饰主语或宾语&lt;/td&gt;&lt;td&gt;&lt;strong&gt;The little&lt;/strong&gt; white rabbit ate &lt;strong&gt;a large&lt;/strong&gt; carrot.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;状语&lt;/td&gt;&lt;td&gt;Adverbial&lt;/td&gt;&lt;td&gt;修饰谓语动词&lt;/td&gt;&lt;td&gt;The rabbit ate &lt;strong&gt;quickly&lt;/strong&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;同位语&lt;/td&gt;&lt;td&gt;Appositive&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Papa Rabbit, &lt;strong&gt;an English teacher&lt;/strong&gt;, eats carrots.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;你可能听过 “&lt;strong&gt;插入语&lt;/strong&gt;”，如：This carrot, I think(插入语), is very tasty. 但是插入语是独立的，不作句子成分。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;5. 复杂句&lt;a href=&quot;#5-复杂句&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;直到现在所说的都是&lt;strong&gt;简单句&lt;/strong&gt;（Simple Sentence），说白了就是没法再拆成更多句子了。但是我们说话写文章可不全是简单句，而是经常会把这些简单句互相组合形成所谓&lt;strong&gt;复合句&lt;/strong&gt;（Compound Sentence）和&lt;strong&gt;复杂句&lt;/strong&gt;（Complex Sentence）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;复合句&lt;/strong&gt;说白了就是句子简单的叠加，句子和句子之间是&lt;strong&gt;并列关系&lt;/strong&gt;，因此也被称为&lt;strong&gt;并列句&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;复杂句&lt;/strong&gt;则是把一个句子套在另一个句子里，允许不断嵌套，是&lt;strong&gt;从属关系&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;一个句子套另一个句子，在英语语法上分别叫做&lt;strong&gt;主句&lt;/strong&gt;（Main Clause）和&lt;strong&gt;从句&lt;/strong&gt;（Subordinate Clause）。&lt;/p&gt;&lt;p&gt;从句说白了就是把简单句修改一下来充当另一个句子的句子成分。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;主句&lt;br /&gt;&lt;br /&gt;Main Clause&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;从句&lt;br /&gt;&lt;br /&gt;Subordinate Clause&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;主句&lt;br /&gt;&lt;br /&gt;Main Clause&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;p&gt;从句&lt;br /&gt;&lt;br /&gt;Subordinate Clause&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;section&gt;&lt;h3&gt;5.1 从句类型&lt;a href=&quot;#51-从句类型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;序号&lt;/th&gt;&lt;th&gt;从句&lt;/th&gt;&lt;th&gt;作用&lt;/th&gt;&lt;th&gt;补充&lt;/th&gt;&lt;th&gt;例句&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;主语从句&lt;/td&gt;&lt;td&gt;作主语&lt;/td&gt;&lt;td&gt;名词性从句&lt;/td&gt;&lt;td&gt;&lt;strong&gt;What Papa Rabbit ate&lt;/strong&gt; is a carrot.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;宾语从句&lt;/td&gt;&lt;td&gt;作宾语&lt;/td&gt;&lt;td&gt;名词性从句&lt;/td&gt;&lt;td&gt;I know &lt;strong&gt;that Papa Rabbit likes carrots&lt;/strong&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;表语从句&lt;/td&gt;&lt;td&gt;作表语&lt;/td&gt;&lt;td&gt;名词性从句&lt;/td&gt;&lt;td&gt;The truth is &lt;strong&gt;that he likes you&lt;/strong&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;同位语从句&lt;/td&gt;&lt;td&gt;作同位语&lt;/td&gt;&lt;td&gt;名词性从句&lt;/td&gt;&lt;td&gt;The fact &lt;strong&gt;that he ate the carrot&lt;/strong&gt; is obvious.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;定语从句&lt;/td&gt;&lt;td&gt;作定语&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;The rabbit &lt;strong&gt;who ate the carrot&lt;/strong&gt; is my papa.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;状语从句&lt;/td&gt;&lt;td&gt;作状语&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;I will leave &lt;strong&gt;when Papa Rabbit comes&lt;/strong&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;6. 词性&lt;a href=&quot;#6-词性&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;但是现在问题来了，两个句子也许句子组成的方式相同，比如都是 “主语 + 谓语动词 + 宾语”，可是句中包含的词的类型并不一定相同，比如：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;The rabbit&lt;/em&gt; &lt;strong&gt;ate&lt;/strong&gt; a carrot.&lt;/p&gt;&lt;p&gt;&lt;em&gt;He&lt;/em&gt; &lt;strong&gt;saw&lt;/strong&gt; something over there.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;很显然，这两句话 “句型” 相同，但是包含的词却不太一样。也就是说，同一类句子成分里可能有不同的词类（或词类），这个概念中文中也有。&lt;/p&gt;

















































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;序号&lt;/th&gt;&lt;th&gt;词性&lt;/th&gt;&lt;th&gt;英文名&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;例句&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;动词&lt;/td&gt;&lt;td&gt;verb&lt;/td&gt;&lt;td&gt;表示动作或状态&lt;/td&gt;&lt;td&gt;Papa Rabbit &lt;strong&gt;eats&lt;/strong&gt; a carrot.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;名词&lt;/td&gt;&lt;td&gt;noun&lt;/td&gt;&lt;td&gt;表示人或物&lt;/td&gt;&lt;td&gt;Papa Rabbit is a &lt;strong&gt;rabbit&lt;/strong&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;冠词&lt;/td&gt;&lt;td&gt;article&lt;/td&gt;&lt;td&gt;用于说明人和事物&lt;/td&gt;&lt;td&gt;Papa Rabbit is &lt;strong&gt;a&lt;/strong&gt; rabbit.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;代词&lt;/td&gt;&lt;td&gt;pronoun&lt;/td&gt;&lt;td&gt;替代人和物&lt;/td&gt;&lt;td&gt;&lt;strong&gt;I&lt;/strong&gt; am a rabbit.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;形容词&lt;/td&gt;&lt;td&gt;adjective&lt;/td&gt;&lt;td&gt;形容人和物&lt;/td&gt;&lt;td&gt;I am a &lt;strong&gt;smart&lt;/strong&gt; rabbit.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;数词&lt;/td&gt;&lt;td&gt;numeral&lt;/td&gt;&lt;td&gt;表数量&lt;/td&gt;&lt;td&gt;I ate &lt;strong&gt;two&lt;/strong&gt; carrots.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;副词&lt;/td&gt;&lt;td&gt;adverb&lt;/td&gt;&lt;td&gt;修饰动作或形容词&lt;/td&gt;&lt;td&gt;I ate two carrots &lt;strong&gt;quickly&lt;/strong&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;介词&lt;/td&gt;&lt;td&gt;preposition&lt;/td&gt;&lt;td&gt;表示和其他词关系的词&lt;/td&gt;&lt;td&gt;I ate two carrots &lt;strong&gt;with&lt;/strong&gt; chopsticks.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;叹词&lt;/td&gt;&lt;td&gt;interjection&lt;/td&gt;&lt;td&gt;表感叹&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Ah&lt;/strong&gt;, the carrot is tasty!&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;连词&lt;/td&gt;&lt;td&gt;conjunction&lt;/td&gt;&lt;td&gt;连接词和句&lt;/td&gt;&lt;td&gt;I ate two carrots and a potato, &lt;strong&gt;because&lt;/strong&gt; I was hungry.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Caution&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;句子成分&lt;/strong&gt;（Clause Element）和句子中的&lt;strong&gt;词类&lt;/strong&gt;（Parts of Speech）是完全不同的概念。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;前文所述的每一种句子成分，除了&lt;strong&gt;谓语动词&lt;/strong&gt;只能是动词，其他成分都可能包含不同的词类。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;7. 谓语动词的 “三大本领”&lt;a href=&quot;#7-谓语动词的-三大本领&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;英语中的谓语动词有很多厉害的本领，而这些本领，中文中的动词都没有，是英语语法的难点之一：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;动作时间&lt;/li&gt;
&lt;li&gt;动作状态&lt;/li&gt;
&lt;li&gt;动作假设，情感等&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;所谓 “中文动词没有这些本领” 是说中文动词本身没有 “时、体、气” 的概念，但是中文当然有相同或相似意思的表达。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;section&gt;&lt;h3&gt;7.1 时态&lt;a href=&quot;#71-时态&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;英语的时态就是&lt;strong&gt;时间&lt;/strong&gt; + &lt;strong&gt;状态&lt;/strong&gt;合在一起，而并非只有时间。&lt;/p&gt;







































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;现在&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;过去&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;将来&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;过去将来&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;em&gt;一般&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;一般&lt;/em&gt;&lt;strong&gt;现在&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;一般&lt;/em&gt;&lt;strong&gt;过去&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;一般&lt;/em&gt;&lt;strong&gt;将来&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;一般&lt;/em&gt;&lt;strong&gt;过去将来&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;em&gt;完成&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;现在&lt;/strong&gt;&lt;em&gt;完成&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;过去&lt;/strong&gt;&lt;em&gt;完成&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;将来&lt;/strong&gt;&lt;em&gt;完成&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;过去将来&lt;/strong&gt;&lt;em&gt;完成&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;em&gt;进行&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;现在&lt;/strong&gt;&lt;em&gt;进行&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;过去&lt;/strong&gt;&lt;em&gt;进行&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;将来&lt;/strong&gt;&lt;em&gt;进行&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;过去将来&lt;/strong&gt;&lt;em&gt;进行&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;em&gt;完成进行&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;现在&lt;/strong&gt;&lt;em&gt;完成进行&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;过去&lt;/strong&gt;&lt;em&gt;完成进行&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;将来&lt;/strong&gt;&lt;em&gt;完成进行&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;过去将来&lt;/strong&gt;&lt;em&gt;完成进行&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;时态是时间的排列组合，并不是一个线性的列表，更不是只和时间有关。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;7.2 语气&lt;a href=&quot;#72-语气&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;序号&lt;/th&gt;&lt;th&gt;语气&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;例句&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;虚拟语气&lt;/td&gt;&lt;td&gt;用来表示意愿，与事实相反的假设等&lt;/td&gt;&lt;td&gt;If I &lt;strong&gt;were&lt;/strong&gt; a rabbit…&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;陈述语气&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;I ate a carrot and…&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;祈使语气&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Eat this carrot and…&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;7.3 助动词（Auxiliary Verb）&lt;a href=&quot;#73-助动词auxiliary-verb&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;英语的谓语动词本身往往是不能够独立完成刚才说的上述的这些本领，而且谓语动词也无法完成比如表示否定、可能性、必须性等意思。&lt;/p&gt;&lt;p&gt;想让谓语动词充分发挥，我们必须用到另一类经常和动词一起用的词，来帮助完成任务，它们就是&lt;strong&gt;助动词&lt;/strong&gt;（Auxiliary Verb）&lt;/p&gt;&lt;p&gt;例如对于动词 eat：&lt;/p&gt;












































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;协助&lt;/th&gt;&lt;th&gt;助动词&lt;/th&gt;&lt;th&gt;组合&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;表示 “吃过了（完成）”&lt;/td&gt;&lt;td&gt;have&lt;/td&gt;&lt;td&gt;have eaten&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;表示 “正在吃（进行）”&lt;/td&gt;&lt;td&gt;be&lt;/td&gt;&lt;td&gt;is eating&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;表示 “被吃”&lt;/td&gt;&lt;td&gt;be&lt;/td&gt;&lt;td&gt;is eaten&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;表示 “有能力吃”&lt;/td&gt;&lt;td&gt;can&lt;/td&gt;&lt;td&gt;can eat&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;表示 “有可能吃”&lt;/td&gt;&lt;td&gt;might&lt;/td&gt;&lt;td&gt;might eat&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;表示 “有义务吃”&lt;/td&gt;&lt;td&gt;must&lt;/td&gt;&lt;td&gt;must eat&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;表示 “不吃（否定）”&lt;/td&gt;&lt;td&gt;do&lt;/td&gt;&lt;td&gt;do not eat&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;以上的 can, might, must 也被称作 “情态动词”，可归类在 “助动词” 里，但是也有些语法体系把情态动词单独归类，这是分类的不同，不影响理解。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Caution&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;有些动词，既可以是助动词（无实义），也可以做实义动词，比如 have 也可以表示 “拥有”。千万不要把助动词和它的其他身份（作实义动词时）弄混淆。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;8. 非谓语动词&lt;a href=&quot;#8-非谓语动词&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;之所以需要把动词叫做谓语动词，是因为还有所谓&lt;strong&gt;非谓语动词&lt;/strong&gt;，这听起来似乎是一句废话。&lt;/p&gt;&lt;p&gt;不过，之所以这么叫，是因为动词除了充当句子中的谓语动词，还有可能充当：主语、宾语、宾语补语、主语补语、定语等，只不过需要变成非谓语动词。&lt;/p&gt;&lt;p&gt;不仅如此，非谓语动词几乎可以取代所有的从句，从而简化句子。只不过非谓语动词就不再具有表示动作时间、状态、语态、语气的功能，也就是它们丧失了时态、语气、语态这些本领了。&lt;/p&gt;&lt;p&gt;非谓语动词以不同的形式出现在句中，包括：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;动词不定式&lt;/strong&gt;：常表将来或具体的单次行为。如作主语：&lt;strong&gt;To eat&lt;/strong&gt; a carrot is Papa Rabbit’s dream.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;现在分词&lt;/strong&gt;：常表主动或正在进行。如作定语：The rabbit &lt;strong&gt;eating&lt;/strong&gt; a carrot is my papa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动名词&lt;/strong&gt;：将动词名词化，常表抽象概念或习惯。如作宾语：Papa Rabbit enjoys &lt;strong&gt;eating&lt;/strong&gt; carrots.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;过去分词&lt;/strong&gt;：常表被动或已经完成。如作宾语补语：Papa Rabbit wants the carrot &lt;strong&gt;eaten&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;9. 总结&lt;a href=&quot;#9-总结&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;上述这么多一直在讲动词，这是因为：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Caution&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;英语语法的核心就是动词，动词几乎可以串联起所有英语语法的核心概念。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/english_grammar_system.png&quot; alt=&quot;英语语法体系&quot; /&gt;&lt;figcaption&gt;英语语法体系&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;以上就是英语语法体系的主体框架，绝大多数的语法规则，都逃不出这个框架。而无数的规则和特例也不过就是让这个核心体系更加丰富而已。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;10. 参考资料&lt;a href=&quot;#10-参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1XY411J7aG?t=0&amp;amp;p=2&quot; target=&quot;_blank&quot;&gt;英语兔：英语语法综述&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mmistakes.github.io/minimal-mistakes/docs/layouts&quot; target=&quot;_blank&quot;&gt;Minimal Mistakes: Layouts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;</content:encoded></item><item><title>GNU 汇编（GAS）通用语法与伪指令指南</title><link>https://bfmhno3.github.io/posts/gnu-assembly/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/gnu-assembly/</guid><description>本文不聚焦某一指令集细节，而是系统梳理 GNU Assembler 的通用语法与伪指令体系，涵盖注释规则、操作数方向、寻址模式、宽度后缀、段与符号控制，以及局部标签与宏等实用写法，帮助你在 x86、ARM、RISC-V 间建立统一理解。</description><pubDate>Sun, 05 Apr 2026 08:35:00 GMT</pubDate><content:encoded>&lt;p&gt;由于机器指令及助记符高度依赖于芯片的具体架构，本文不讨论特定指令集的全部细节，而是聚焦于 GAS（GNU Assembler）通用的伪指令系统及其基本语法规则。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;1. 汇编器的本质与工作流&lt;a href=&quot;#1-汇编器的本质与工作流&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;GNU Assembler（简称 &lt;code&gt;as&lt;/code&gt;）的核心工作流可以用以下公式概括：&lt;/p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;源代码 (.s)→汇编器 (as)可重定位目标文件 (.o)\text{源代码 (.s)} \xrightarrow{\text{汇编器 (as)}} \text{可重定位目标文件 (.o)}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;源代码&lt;/span&gt;&lt;span&gt; (.s)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;汇编器&lt;/span&gt;&lt;span&gt; (as)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;可重定位目标文件&lt;/span&gt;&lt;span&gt; (.o)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;p&gt;生成的 &lt;code&gt;.o&lt;/code&gt; 文件主要由 &lt;strong&gt;Section&lt;/strong&gt;（段）和 &lt;strong&gt;Symbol Table&lt;/strong&gt;（符号表）组成。我们常用的 GAS 伪指令（Directives/Pseudo-ops），其根本目的就是为了控制这两个核心要素的生成。&lt;/p&gt;&lt;p&gt;从本质上来说，GNU Assembler 的核心职能并非简单的 “代码翻译”，而是&lt;strong&gt;内存空间的规划&lt;/strong&gt;与&lt;strong&gt;符号地址的解析&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;汇编器内部维护着一个至关重要的变量：&lt;strong&gt;地址计数器&lt;/strong&gt;（Location Counter，通常记为 &lt;code&gt;.&lt;/code&gt;）。当汇编器读取源代码时，它实际上是在执行一个状态机循环：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;解析伪指令&lt;/strong&gt;：根据指令调整地址计数器的位置（例如跳转到新的段、预留空白空间）或修改符号的属性（如设置为全局可见）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;发射字节流&lt;/strong&gt;：将翻译好的机器指令或定义的静态数据 “填充” 到地址计数器当前指向的内存单元，随后让计数器自增，指向下一个空闲位置。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构建符号表&lt;/strong&gt;：记录所有标号（Label）对应的最终物理地址或相对于段的偏移量。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;因此，当我们编写汇编代码时，实际上是在做两件事：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;填充内存&lt;/strong&gt;：明确告诉汇编器，在当前的内存地址上应当放置什么数据（注意：机器指令在内存中本质上也是数据）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标记位置&lt;/strong&gt;：给特定的内存地址起一个人类可读的名字（Label/Symbol），以便后续进行跳转或读写操作。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2. 指令语法&lt;a href=&quot;#2-指令语法&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;GAS 语法的核心难点在于不同架构下对&lt;strong&gt;操作数方向&lt;/strong&gt;的处理，以及如何消除符号的&lt;strong&gt;歧义&lt;/strong&gt;。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;2.1 注释风格&lt;a href=&quot;#21-注释风格&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;不同的 CPU 架构在 GAS 中通常使用不同的单行注释符号。这里仅列举常见架构的规范，更详细的列表可查阅 &lt;a href=&quot;https://sourceware.org/binutils/docs/as/Machine-Dependencies.html&quot; target=&quot;_blank&quot;&gt;Binutils Docs: Machine Dependent Features&lt;/a&gt;。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;推荐直接查阅 “&lt;a href=&quot;https://sourceware.org/binutils/docs/as/Machine-Dependencies.html&quot; target=&quot;_blank&quot;&gt;Machine Dependent Features&lt;/a&gt;” 文档，官方通用的 “&lt;a href=&quot;https://sourceware.org/binutils/docs/as/Comments.html&quot; target=&quot;_blank&quot;&gt;Comments&lt;/a&gt;” 文档包含大量历史遗留信息，较为混乱。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;多行注释&lt;/strong&gt;在所有架构中是通用的，沿用了 C 语言风格：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/*&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;The only way to include a newline (&apos;\n&apos;) in a comment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;is to use this sort of comment.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/* This sort of comment does not nest. */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;单行注释&lt;/strong&gt;则因架构而异：&lt;/p&gt;&lt;section&gt;&lt;h4&gt;2.1.1 x86-64&lt;a href=&quot;#211-x86-64&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;根据 &lt;a href=&quot;https://sourceware.org/binutils/docs/as/i386_002dChars.html&quot; target=&quot;_blank&quot;&gt;Binutils Docs: i386_002dChars&lt;/a&gt; 的说明：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;The presence of a &lt;code&gt;#&lt;/code&gt; appearing anywhere on a line indicates the start of a comment that extends to the end of that line.&lt;/p&gt;&lt;p&gt;If a &lt;code&gt;#&lt;/code&gt; appears as the first character of a line then the whole line is treated as a comment, but in this case the line can also be a logical line number directive…&lt;/p&gt;&lt;p&gt;If the —divide command line option has not been specified then the `/’ character appearing anywhere on a line also introduces a line comment.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;;&lt;/code&gt; character can be used to separate statements on the same line.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;核心解读：&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;首选注释符&lt;/strong&gt;：使用 &lt;code&gt;#&lt;/code&gt;。只要行内出现 &lt;code&gt;#&lt;/code&gt;，其后内容均为注释。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特殊情况&lt;/strong&gt;：如果 &lt;code&gt;#&lt;/code&gt; 出现在行首，汇编器会优先检查它是否为预处理命令（如 &lt;code&gt;#include&lt;/code&gt;）或逻辑行号。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避坑指南&lt;/strong&gt;：虽然默认情况下 &lt;code&gt;/&lt;/code&gt; 也可以作为注释开始，但这极易产生歧义。例如在指令 &lt;code&gt;movq $(10/2), %rax&lt;/code&gt; 中，汇编器可能会把 &lt;code&gt;/2&lt;/code&gt; 误判为注释。除非指定了 &lt;code&gt;--divide&lt;/code&gt; 选项，否则&lt;strong&gt;严禁使用 &lt;code&gt;/&lt;/code&gt; 写注释&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语句分隔&lt;/strong&gt;：分号 &lt;code&gt;;&lt;/code&gt; 用于在同一行写多条指令，类似 C 语言。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;2.1.2 ARM (32-bit)&lt;a href=&quot;#212-arm-32-bit&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;根据 &lt;a href=&quot;https://sourceware.org/binutils/docs/as/ARM_002dChars.html&quot; target=&quot;_blank&quot;&gt;Binutils Docs: ARM_002dChars&lt;/a&gt; 的说明：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;The presence of a &lt;code&gt;@&lt;/code&gt; anywhere on a line indicates the start of a comment that extends to the end of that line.&lt;/p&gt;&lt;p&gt;If a &lt;code&gt;#&lt;/code&gt; appears as the first character of a line then the whole line is treated as a comment…&lt;/p&gt;&lt;p&gt;The &lt;code&gt;;&lt;/code&gt; character can be used instead of a newline to separate statements.&lt;/p&gt;&lt;p&gt;Either &lt;code&gt;#&lt;/code&gt; or &lt;code&gt;$&lt;/code&gt; can be used to indicate immediate operands.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;核心解读：&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;首选注释符&lt;/strong&gt;：使用 &lt;code&gt;@&lt;/code&gt;。这是最安全的方式。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分号的作用&lt;/strong&gt;：在 GNU ARM 汇编中，&lt;code&gt;;&lt;/code&gt; 是语句分隔符（注意：这与 ARM 官方汇编器如 Keil/armasm 不同，后者用 &lt;code&gt;;&lt;/code&gt; 做注释）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;#&lt;/code&gt; 的双重身份&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;如果在&lt;strong&gt;行首&lt;/strong&gt;，它是注释。&lt;/li&gt;
&lt;li&gt;如果在&lt;strong&gt;指令中&lt;/strong&gt;，它表示立即数（例如 &lt;code&gt;MOV R0, #10&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;警告&lt;/strong&gt;：切勿在指令行尾使用 &lt;code&gt;#&lt;/code&gt; 做注释，汇编器会将其误认为是非法的立即数格式。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;立即数&lt;/strong&gt;：虽然支持 x86 风格的 &lt;code&gt;$&lt;/code&gt;，但为了代码移植性，&lt;strong&gt;强烈建议使用 &lt;code&gt;#&lt;/code&gt; 表示立即数&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;2.1.3 AArch64 (ARM64)&lt;a href=&quot;#213-aarch64-arm64&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;GNU 对 32 位 ARM 和 64 位 AArch64 做了严格区分，语法有所不同。&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://sourceware.org/binutils/docs/as/AArch64_002dChars.html&quot; target=&quot;_blank&quot;&gt;Binutils Docs: AArch64_002dChars&lt;/a&gt; 说明如下：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;The presence of a &lt;code&gt;//&lt;/code&gt; on a line indicates the start of a comment that extends to the end of the current line.&lt;/p&gt;&lt;p&gt;If a &lt;code&gt;#&lt;/code&gt; appears as the first character of a line, the whole line is treated as a comment.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;#&lt;/code&gt; can be optionally used to indicate immediate operands.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;核心解读：&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;首选注释符&lt;/strong&gt;：使用 &lt;code&gt;//&lt;/code&gt;（C++ 风格）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;立即数&lt;/strong&gt;：&lt;code&gt;#&lt;/code&gt; 是可选的，但在阅读习惯上保留它更为清晰。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;2.1.4 RISC-V&lt;a href=&quot;#214-risc-v&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;RISC-V 的 GAS 实现较为现代，通常兼容性较好：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;支持行首 &lt;code&gt;#&lt;/code&gt; 注释。&lt;/li&gt;
&lt;li&gt;通常支持 &lt;code&gt;//&lt;/code&gt; 或 &lt;code&gt;#&lt;/code&gt; 作为行尾注释（具体取决于工具链版本，建议测试确认）。&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;建议以 &lt;code&gt;#&lt;/code&gt; 为主，不建议使用 &lt;code&gt;//&lt;/code&gt; 作为通用写法。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.2 最大的分歧：数据流向（Direction）&lt;a href=&quot;#22-最大的分歧数据流向direction&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;阅读 GAS 代码的第一道门槛是&lt;strong&gt;源操作数与目的操作数的顺序&lt;/strong&gt;。GAS 历史上继承了 &lt;strong&gt;AT&amp;amp;T&lt;/strong&gt; 语法（源自 Unix），而芯片厂商文档通常使用 &lt;strong&gt;Intel/Native&lt;/strong&gt; 语法。&lt;/p&gt;&lt;section&gt;&lt;h4&gt;2.2.1 x86 环境下的 GAS（AT&amp;amp;T 风格）&lt;a href=&quot;#221-x86-环境下的-gasatt-风格&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;x86 GAS 遵循 “&lt;strong&gt;源在前，目的在后&lt;/strong&gt;” 的原则。读代码时请默念“把左边的值给右边”。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;operation source, destination&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;例如：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;movl %&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;ebx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt; # 含义：EBX = EAX (将 EAX 的值搬运到 EBX)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;2.2.2 ARM/RISC-V 环境下的 GAS（官方风格）&lt;a href=&quot;#222-armrisc-v-环境下的-gas官方风格&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;为了减少混乱，GNU 针对 ARM 和 RISC-V 做了妥协，采用了更符合官方文档的 “&lt;strong&gt;目的在前，源在后&lt;/strong&gt;” 顺序：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;operation destination, source&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;例如：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;mov&lt;/span&gt;&lt;span&gt; r0, r1      &lt;/span&gt;&lt;/span&gt;&lt;span&gt; # 含义：r0 = r1 (将 r1 的值搬运到 r0)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.3 消除歧义：前缀与修饰符&lt;a href=&quot;#23-消除歧义前缀与修饰符&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;汇编器必须通过“前缀”来区分一个数字到底是&lt;strong&gt;数值&lt;/strong&gt;、&lt;strong&gt;地址&lt;/strong&gt;还是&lt;strong&gt;寄存器&lt;/strong&gt;。&lt;/p&gt;&lt;section&gt;&lt;h4&gt;2.3.1 寄存器&lt;a href=&quot;#231-寄存器&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;x86 (AT&amp;amp;T)&lt;/strong&gt;：必须加 &lt;code&gt;%&lt;/code&gt;，例如 &lt;code&gt;%eax&lt;/code&gt;, &lt;code&gt;%esp&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ARM/RISC-V&lt;/strong&gt;：通常不加前缀，直接写 &lt;code&gt;r0&lt;/code&gt;, &lt;code&gt;sp&lt;/code&gt;, &lt;code&gt;pc&lt;/code&gt;。为了避免与 C 语言变量名冲突，偶尔可见 &lt;code&gt;%r0&lt;/code&gt; 写法，但不常见。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;2.3.2 立即数&lt;a href=&quot;#232-立即数&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;汇编器如何知道 &lt;code&gt;10&lt;/code&gt; 是指“内存地址 10”还是“数字 10”？&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;x86 (AT&amp;amp;T)&lt;/strong&gt;：数字前必须加 &lt;code&gt;$&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ARM&lt;/strong&gt;：官方推荐加 &lt;code&gt;#&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RISC-V&lt;/strong&gt;：极其精简，通常不加前缀，依靠指令名（如 &lt;code&gt;li&lt;/code&gt;, &lt;code&gt;addi&lt;/code&gt;）的上下文自动推断。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;对比示例：&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# x86 (AT&amp;amp;T)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;movl &lt;/span&gt;&lt;span&gt;$10&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt; # EAX = 10 (立即数)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;movl &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt; # EAX = *(int*)(0x0A) (读取内存地址 10 的内容)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# ARM&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;MOV&lt;/span&gt;&lt;span&gt; R0, #&lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;      @ R0 = &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# RISC-V&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;li a0, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt; # Load Immediate: a0 = 10&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.4 内存寻址模式&lt;a href=&quot;#24-内存寻址模式&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在 64 位系统中，内存地址空间高达 &lt;span&gt;&lt;span&gt;2642^{64}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;64&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，需要 8 字节才能完整表达。然而，一条机器指令通常只有 4 字节（RISC）或变长（x86）。如何在有限的指令长度内精确指向遥远的内存地址？这是寻址模式设计的核心挑战。&lt;/p&gt;&lt;section&gt;&lt;h4&gt;2.4.1 x86 (AT&amp;amp;T)&lt;a href=&quot;#241-x86-att&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;x86 是 CISC（复杂指令集），其最大特点是&lt;strong&gt;允许运算指令直接操作内存&lt;/strong&gt;。硬件内建了强大的 SIB（Scale-Index-Base）寻址单元，将地址计算视为一个多项式：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;通用格式&lt;/strong&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;offset(base, index, scale)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;计算公式&lt;/strong&gt;：&lt;/p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Address=base+(index×scale)+offset\text{Address} = \text{base} + (\text{index} \times \text{scale}) + \text{offset}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Address&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;base&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;index&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;×&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;scale&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;offset&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;p&gt;&lt;strong&gt;应用场景&lt;/strong&gt;：这非常适合访问数组。例如 &lt;code&gt;array[i]&lt;/code&gt;，其中 &lt;code&gt;base&lt;/code&gt; 是数组首地址，&lt;code&gt;index&lt;/code&gt; 是变量 &lt;code&gt;i&lt;/code&gt;，&lt;code&gt;scale&lt;/code&gt; 是元素大小（如 &lt;code&gt;int&lt;/code&gt; 为 4）。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;movl &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;(%&lt;/span&gt;&lt;span&gt;ebp&lt;/span&gt;&lt;span&gt;), %&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt; # 取地址 (ebp + 4) 处的数据 -&amp;gt; eax&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;movl array(,%&lt;/span&gt;&lt;span&gt;edi&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;), %&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;&lt;span&gt; # 取地址 (array + edi * 4) 处的数据 -&amp;gt; eax&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;2.4.2 ARM&lt;a href=&quot;#242-arm&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;ARM 是 RISC 且为 &lt;strong&gt;Load/Store 架构&lt;/strong&gt;。这意味着不能直接对内存里的数进行 &lt;code&gt;add&lt;/code&gt; 等运算，必须先 &lt;code&gt;ldr&lt;/code&gt;（Load）到寄存器，运算后再 &lt;code&gt;str&lt;/code&gt;（Store）回去。&lt;/p&gt;&lt;p&gt;ARM 的特色在于 &lt;strong&gt;Address Write-back（地址回写）&lt;/strong&gt;。为了方便遍历数组，它允许在读写内存的同时，自动更新基址寄存器的值。&lt;/p&gt;&lt;p&gt;GAS 中使用中括号 &lt;code&gt;[]&lt;/code&gt; 表示内存引用：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;常规偏移&lt;/strong&gt;：&lt;code&gt;[Base, Offset]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;寄存器偏移&lt;/strong&gt;：&lt;code&gt;[Base, Register]&lt;/code&gt;，甚至支持移位 &lt;code&gt;[Base, Reg, lsl #3]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;前索引（Pre-indexed）&lt;/strong&gt;：&lt;code&gt;[r1, #8]!&lt;/code&gt; —— 访问前先加 8，且&lt;strong&gt;修改&lt;/strong&gt; x1 的值（x1 += 8）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后索引（Post-indexed）&lt;/strong&gt;：&lt;code&gt;[r1], #8&lt;/code&gt; —— 访问后再加 8，且&lt;strong&gt;修改&lt;/strong&gt; x1 的值。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;2.4.3 RISC-V&lt;a href=&quot;#243-risc-v&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;RISC-V 追求极致的硬件精简。它不支持 ARM 那种复杂的自动回写，也不支持 x86 的复杂比例因子。&lt;/p&gt;&lt;p&gt;RISC-V 的基础指令多为 32 bit（但启用压缩扩展后也有 16 bit 指令），除去操作码和寄存器位，留给立即数偏移的空间只有 &lt;strong&gt;12 位&lt;/strong&gt;（即 &lt;span&gt;&lt;span&gt;±2KB\pm 2\text{KB}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;±&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt;KB&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;通用格式&lt;/strong&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;offset(base)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;计算公式&lt;/strong&gt;：&lt;/p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Address=base+offset\text{Address} = \text{base} + \text{offset}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;Address&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;base&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;offset&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;p&gt;&lt;strong&gt;如何访问大范围地址？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;由于 12 位无法容纳 32/64 位地址，必须将大地址拆分为“高位”和“低位”两条指令处理：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 小范围寻址 (Offset &amp;lt; 2048)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;lw a0, &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sp&lt;/span&gt;&lt;span&gt;)       &lt;/span&gt;&lt;span&gt; # 从 sp + 8 处加载一个 word&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;sw a0, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;(t1)       &lt;/span&gt;&lt;span&gt; # 存入 t1 + 0 处&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 大范围/全局变量寻址 (Hi/Lo 拆分)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;lui t0, %hi(VAL)     &lt;/span&gt;&lt;span&gt; # 加载 VAL 地址的高 20 位到 t0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;lw  a0, %lo(VAL)(t0) &lt;/span&gt;&lt;span&gt; # 加载 VAL 地址的低 12 位，作为偏移加到 t0 上读取&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.5 指令后缀：操作数宽度 (Size Suffixes)&lt;a href=&quot;#25-指令后缀操作数宽度-size-suffixes&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在机器层面，CPU 处理数据时必须明确一个核心参数：&lt;strong&gt;位宽 (Width)&lt;/strong&gt;。硬件需要知道它是应该只驱动数据总线上的 8 根线，还是激活全部 64 根线？是将数据放入寄存器的低 8 位（如 &lt;code&gt;AL&lt;/code&gt;），还是覆盖整个 64 位（如 &lt;code&gt;RAX&lt;/code&gt;）？&lt;/p&gt;&lt;p&gt;GAS 通过在指令助记符后添加&lt;strong&gt;后缀&lt;/strong&gt;来显式控制这一行为。&lt;/p&gt;&lt;section&gt;&lt;h4&gt;2.5.1 GAS 通用命名规则&lt;a href=&quot;#251-gas-通用命名规则&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;GAS 使用一套单字母后缀来表示数据宽度。需要特别警惕的是，不同架构对 “Word” 的定义存在历史分歧。&lt;/p&gt;







































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;后缀&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;全称&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;大小 (Bits)&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;字节数&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;说明&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;b&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;B&lt;/strong&gt;yte&lt;/td&gt;&lt;td&gt;8-bit&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;通用。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;w&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;W&lt;/strong&gt;ord&lt;/td&gt;&lt;td&gt;&lt;strong&gt;16-bit&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;&lt;strong&gt;注意！&lt;/strong&gt; 在 x86 语境下，Word 是 16 位（源于 8086）。但在 ARM/RISC-V 文档中，Word 通常指 32 位。GAS 的 &lt;code&gt;w&lt;/code&gt; 后缀专指 16 位。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;l&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;L&lt;/strong&gt;ong&lt;/td&gt;&lt;td&gt;32-bit&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;x86 的标准 32 位后缀。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;q&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Q&lt;/strong&gt;uad&lt;/td&gt;&lt;td&gt;64-bit&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;四字（4 x 16-bit），即 64 位。&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;2.5.2 x86 架构：显式后缀 (Explicit Suffixes)&lt;a href=&quot;#252-x86-架构显式后缀-explicit-suffixes&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;x86 是变长指令集，且寄存器存在嵌套关系（&lt;code&gt;RAX&lt;/code&gt; 包含 &lt;code&gt;EAX&lt;/code&gt; 包含 &lt;code&gt;AX&lt;/code&gt; 包含 &lt;code&gt;AL&lt;/code&gt;）。为了消除歧义，GAS &lt;strong&gt;强烈推荐&lt;/strong&gt;在所有操作码后加上后缀。&lt;/p&gt;&lt;p&gt;虽然现代汇编器有时能根据寄存器名字（如 &lt;code&gt;%eax&lt;/code&gt;）推断出你是想操作 32 位，但在涉及内存操作数（无法看出大小）或立即数时，不写后缀极易报错。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;movb &lt;/span&gt;&lt;span&gt;$0xFF&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;/span&gt;&lt;span&gt;al&lt;/span&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt; # 移动 1 个字节 (8-bit)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;movw &lt;/span&gt;&lt;span&gt;$0xFFFF&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;/span&gt;&lt;span&gt;ax&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt; # 移动 1 个字   (16-bit)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;movl &lt;/span&gt;&lt;span&gt;$0xFFFFFFFF&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt; # 移动 1 个长字 (32-bit)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;movq&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$1&lt;/span&gt;&lt;span&gt;, %&lt;/span&gt;&lt;span&gt;rax&lt;/span&gt;&lt;span&gt;         &lt;/span&gt;&lt;span&gt; # 移动 1 个四字 (64-bit)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 歧义示例：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# mov $0, (%rbx)     # 错误！汇编器不知道你想把 rb 指向的内存清零 1 个字节还是 8 个字节。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;movq&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$0&lt;/span&gt;&lt;span&gt;, (%&lt;/span&gt;&lt;span&gt;rbx&lt;/span&gt;&lt;span&gt;)     &lt;/span&gt;&lt;span&gt; # 正确：清零 8 个字节&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;2.5.3 ARM / RISC-V 架构：助记符内含宽度&lt;a href=&quot;#253-arm--risc-v-架构助记符内含宽度&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;与 x86 不同，ARM 和 RISC-V 等 RISC 架构通常将数据宽度直接&lt;strong&gt;编码在指令助记符本身&lt;/strong&gt;中，而不是依赖 GAS 的通用后缀。&lt;/p&gt;&lt;p&gt;在这些架构中，你通常不会看到 &lt;code&gt;movl&lt;/code&gt; 这种写法，而是通过不同的指令变体来实现：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RISC-V&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lb&lt;/code&gt; (Load Byte): 加载 8 位&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lh&lt;/code&gt; (Load Half-word): 加载 16 位&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lw&lt;/code&gt; (Load Word): 加载 32 位 (&lt;strong&gt;注意：这里 Word 是 32 位&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ld&lt;/code&gt; (Load Double-word): 加载 64 位&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ARM&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LDRB&lt;/code&gt; (Byte): 加载 8 位&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LDRH&lt;/code&gt; (Half-word): 加载 16 位&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LDR&lt;/code&gt; (Word): 加载 32 位&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;“Word” 的陷阱&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这是一个跨架构开发时最容易混淆的概念：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;在 &lt;strong&gt;x86&lt;/strong&gt; 汇编中，&lt;code&gt;Word&lt;/code&gt; = &lt;strong&gt;16 bits&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;在 &lt;strong&gt;ARM / RISC-V&lt;/strong&gt; 汇编中，&lt;code&gt;Word&lt;/code&gt; = &lt;strong&gt;32 bits&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;当你使用 GAS 的 &lt;code&gt;.word&lt;/code&gt; 伪指令定义数据时，它在所有架构上通常都表示 32 位（或 16 位，取决于具体配置，但在现代环境下多为 32 位）。务必查阅具体目标架构的文档确认 &lt;code&gt;.word&lt;/code&gt; 的确切长度，或者直接使用 &lt;code&gt;.2byte&lt;/code&gt; / &lt;code&gt;.4byte&lt;/code&gt; 这种无歧义的写法。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;3. 伪指令系统 (Directives)&lt;a href=&quot;#3-伪指令系统-directives&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;伪指令以 &lt;code&gt;.&lt;/code&gt; 开头，它们不对应具体的 CPU 机器码，而是指挥汇编器如何组织二进制结构。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;3.1 数据定义：如何填充字节&lt;a href=&quot;#31-数据定义如何填充字节&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这些指令直接在当前地址计数器位置“发射”数据，常用于定义查找表或初始化变量。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.byte&lt;/code&gt; / &lt;code&gt;.hword&lt;/code&gt; / &lt;code&gt;.word&lt;/code&gt; / &lt;code&gt;.long&lt;/code&gt; / &lt;code&gt;.quad&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;在当前地址写入特定宽度的整数。注意**字节序（Endianness）**取决于目标架构（x86 为小端，ARM/RISC-V 默认为小端，但可配置）。&lt;/p&gt;
&lt;p&gt;代码段&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0x12&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0x34&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt; # 写入两个字节&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;word&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0xAABBCCDD&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt; # 写入 4 字节。小端模式下内存为：DD CC BB AA&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.ascii&lt;/code&gt; vs &lt;code&gt;.asciz&lt;/code&gt; / &lt;code&gt;.string&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.ascii &quot;Hello&quot;&lt;/code&gt;：仅填入字符 ASCII 码，&lt;strong&gt;无&lt;/strong&gt;结尾 &lt;code&gt;\0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.asciz &quot;Hello&quot;&lt;/code&gt; 或 &lt;code&gt;.string&lt;/code&gt;：自动在末尾补 &lt;code&gt;0x00&lt;/code&gt;（C 语言标准字符串）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.fill&lt;/code&gt; / &lt;code&gt;.skip&lt;/code&gt; / &lt;code&gt;.space&lt;/code&gt; / &lt;code&gt;.zero&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;用于预留空间或清零，常用于定义栈（Stack）或 BSS 段。&lt;/p&gt;
&lt;p&gt;代码段&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;stack_top:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.skip &lt;/span&gt;&lt;span&gt;1024&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt; # 预留 1024 字节，全部填 0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;stack_bottom:&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt; # 栈底标号（假设栈向下生长，SP 初始指向此处）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.2 内存控制：如何管理地址&lt;a href=&quot;#32-内存控制如何管理地址&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.section&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;告诉汇编器：“&lt;strong&gt;接下来的数据/指令，请分类归档到哪个‘抽屉’里&lt;/strong&gt;”。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.section .text&lt;/code&gt;：代码段（只读，可执行）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.section .data&lt;/code&gt;：已初始化的全局变量（可读写）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.section .bss&lt;/code&gt;：未初始化变量（不占磁盘空间，程序启动时自动清零）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自定义段&lt;/strong&gt;：例如 &lt;code&gt;.section .isr_vector&lt;/code&gt;，配合链接脚本（Linker Script），可以将中断向量表精确固定在 Flash 的 0 地址处。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.align&lt;/code&gt; vs &lt;code&gt;.balign&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.align&lt;/code&gt;：行为不统一！在 x86 下 &lt;code&gt;.align 4&lt;/code&gt; 表示 4 字节对齐；但在 ARM 下可能表示 &lt;span&gt;&lt;span&gt;24=162^4=16&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;4&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 字节对齐。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.balign&lt;/code&gt;（Byte Align）：&lt;strong&gt;推荐使用&lt;/strong&gt;。&lt;code&gt;.balign 4, 0&lt;/code&gt; 明确表示保证地址能被 4 整除，如果需要填充，则填 0。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.org&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;强制修改当前的地址计数器。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;示例&lt;/strong&gt;：编写 MBR 引导扇区时，需要在第 510 字节写结束标志 &lt;code&gt;0x55AA&lt;/code&gt;，会用到 &lt;code&gt;.org 510&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.3 符号管理：与 C 语言的交互接口&lt;a href=&quot;#33-符号管理与-c-语言的交互接口&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是 C 和汇编混合编程的桥梁。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.global&lt;/code&gt;（或 &lt;code&gt;.globl&lt;/code&gt;）：&lt;/p&gt;
&lt;p&gt;将符号标记为&lt;strong&gt;全局可见&lt;/strong&gt;。如果不加此标记，符号仅在当前文件内部可见（类似 C 的 &lt;code&gt;static&lt;/code&gt;），链接器无法找到它。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;场景&lt;/strong&gt;：Startup 文件（启动文件）必须声明 &lt;code&gt;.global Reset_Handler&lt;/code&gt;，否则硬件复位后找不到入口。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.extern&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;声明符号在外部定义。虽然 GAS 通常能自动推断，但显式声明能提高代码可读性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.equ&lt;/code&gt; / &lt;code&gt;.set&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;常量定义，相当于 C 的 &lt;code&gt;#define&lt;/code&gt;。它们只存在于汇编阶段，不占用运行时的内存空间。&lt;/p&gt;
&lt;p&gt;代码段&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;equ&lt;/span&gt;&lt;span&gt; STACK_SIZE, &lt;/span&gt;&lt;span&gt;0x400&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.type&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;明确告诉链接器符号的类型（是函数还是数据）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;重要性&lt;/strong&gt;：在 ARM Thumb-2 指令集中，如果链接器知道某符号是 &lt;code&gt;%function&lt;/code&gt;，它会在计算跳转地址时自动将最低位置 1（Thumb 状态标志）。若省略此指令，可能导致切换状态失败而进入 HardFault。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.weak&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;弱符号定义&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;机制&lt;/strong&gt;：如果在其他地方定义了同名的强符号（普通的 &lt;code&gt;.global&lt;/code&gt;），链接器优先使用那个；如果没有，则使用这里的默认定义。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用&lt;/strong&gt;：STM32 启动文件中的中断处理函数通常用 &lt;code&gt;.weak&lt;/code&gt; 定义为死循环。这允许用户在 C 代码中重写同名函数（如 &lt;code&gt;SysTick_Handler&lt;/code&gt;）来覆盖默认行为，而无需修改启动代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;4. GAS 的特色功能 (Syntactic Sugar)&lt;a href=&quot;#4-gas-的特色功能-syntactic-sugar&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;4.1 局部数值标签 (Local Numeric Labels)&lt;a href=&quot;#41-局部数值标签-local-numeric-labels&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;为了避免给简单的循环绞尽脑汁起名字（如 &lt;code&gt;loop1&lt;/code&gt;, &lt;code&gt;loop_start&lt;/code&gt;），GAS 允许使用数字作为临时标号。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;定义&lt;/strong&gt;：&lt;code&gt;&amp;lt;num&amp;gt;:&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;引用&lt;/strong&gt;：&lt;code&gt;&amp;lt;num&amp;gt;f&lt;/code&gt; (forward, 向前/向下找)，&lt;code&gt;&amp;lt;num&amp;gt;b&lt;/code&gt; (backward, 向后/向上找)。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;&lt;p&gt;代码段&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;:         &lt;/span&gt;&lt;span&gt; # 定义标号 1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;wfi    &lt;/span&gt;&lt;span&gt; # Wait for interrupt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;j &lt;/span&gt;&lt;span&gt;1b&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt; # 向后跳转到最近的标号 1 (相当于 while(1);)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4.2 宏 (Macros)&lt;a href=&quot;#42-宏-macros&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;用于封装重复代码片段：&lt;/p&gt;&lt;p&gt;代码段&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.macro 宏名称 参数1, 参数2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 宏体内容，可使用 \参数1 引用&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.endm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4.3 &lt;code&gt;.&lt;/code&gt; (Current Location)&lt;a href=&quot;#43--current-location&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;符号 &lt;code&gt;.&lt;/code&gt; 代表当前地址计数器的值。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;常见用法&lt;/strong&gt;：&lt;code&gt;b .&lt;/code&gt; 等同于 &lt;code&gt;while (1);&lt;/code&gt;（跳转到当前地址，死循环）。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>ARMv7-A 指令体系全解：数据处理、访存、分支与同步</title><link>https://bfmhno3.github.io/posts/armv7-a-isa/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/armv7-a-isa/</guid><description>以 I.MX6ULL（ARM Cortex-A7）为背景，从物理本质出发，系统梳理 ARMv7-A 指令集的核心要点：定长 32 位指令结构与条件执行机制、16 个核心寄存器的 AAPCS 调用规范，以及数据处理、内存访问、分支控制、系统状态和原子同步五大类指令的功能与应用场景。</description><pubDate>Sun, 05 Apr 2026 08:20:00 GMT</pubDate><content:encoded>&lt;p&gt;I.MX6ULL 的核心是 ARM Cortex-A7，它基于 ARMv7-A 架构。&lt;/p&gt;
&lt;p&gt;要真正理解指令集，就不能只是背诵汇编手册，而是要回答一个核心问题：如果不看代码，只看电路，CPU 在做什么？&lt;/p&gt;
&lt;p&gt;本质上，每一条指令都是对 CPU 内部寄存器状态或外部&lt;strong&gt;总线&lt;/strong&gt;（内存/外设）的一次&lt;strong&gt;原子操作&lt;/strong&gt;。&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;总线&lt;/strong&gt;（Bus）通俗理解就是主板上的 “高速公路”。它的硬件本质则是一组并排的导线，通过这组导线，连接 CPU 内部各部分或连接 CPU 和外设。&lt;/p&gt;&lt;p&gt;总线分为&lt;strong&gt;数据总线&lt;/strong&gt;（传数据）、&lt;strong&gt;地址总线&lt;/strong&gt;（告诉内存我要读哪一个单元）和&lt;strong&gt;控制总线&lt;/strong&gt;（读还是写）。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;原子操作&lt;/strong&gt;（Atomic Operation）通俗理解就是，要么做完，要么完全不做，中间绝不允许被打断的操作。&lt;/p&gt;&lt;p&gt;在 ARM 中，单条读写指令通常是原子的，但 “读-改-写” 三步操作默认不是原子的，需要特殊指令（&lt;code&gt;ldrex&lt;/code&gt; / &lt;code&gt;strex&lt;/code&gt;） 保证。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;section&gt;&lt;h2&gt;1. 物理本质&lt;a href=&quot;#1-物理本质&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在 ARM 状态下（Cortex-A7 上电默认状态），每一条指令都是严格的 32 位（4 字节）二进制码。&lt;/p&gt;&lt;p&gt;与 x86（变长指令）不同，ARM 的定长指令让译码器设计更加简洁高效。一条典型的 ARM 指令在物理电路上被切割为以下结构：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Instruction&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [Codition &lt;/span&gt;&lt;span&gt;Code&lt;/span&gt;&lt;span&gt; (4 &lt;/span&gt;&lt;span&gt;bits&lt;/span&gt;&lt;span&gt;)] + [OpCode &amp;amp; Flags] + [Operands (&lt;/span&gt;&lt;span&gt;Registers&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;Immediates&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;section&gt;&lt;h3&gt;1.1 核心差异：条件执行（Conditional Execution）&lt;a href=&quot;#11-核心差异条件执行conditional-execution&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是 ARM 架构最吸引人的设计之一。几乎每一条指令的高 4 位（Bits 31-28）都是条件码。这意味着：&lt;strong&gt;任何指令都可以是条件跳转指令的变体&lt;/strong&gt;。&lt;/p&gt;







































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;条件后缀&lt;/th&gt;&lt;th&gt;标志位要求（CPSR）&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;汇编举例&lt;/th&gt;&lt;th&gt;C 语言映射&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;eq&lt;/td&gt;&lt;td&gt;&lt;code&gt;Z == 1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Equal（相等）&lt;/td&gt;&lt;td&gt;&lt;code&gt;addeq r0, r1, r2&lt;/code&gt;&lt;br /&gt;（如果相等，则相加）&lt;/td&gt;&lt;td&gt;&lt;code&gt;if (a == b)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ne&lt;/td&gt;&lt;td&gt;&lt;code&gt;Z == 0&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Not Equal（不想等）&lt;/td&gt;&lt;td&gt;&lt;code&gt;movne r0, #1&lt;/code&gt;&lt;br /&gt;（如果不等，则赋值）&lt;/td&gt;&lt;td&gt;&lt;code&gt;if (a != b)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;al&lt;/td&gt;&lt;td&gt;Ignored&lt;/td&gt;&lt;td&gt;Always（默认）&lt;/td&gt;&lt;td&gt;&lt;code&gt;add r0, r1, r2&lt;/code&gt;&lt;br /&gt;（指令不写后缀默认就是 AL）&lt;/td&gt;&lt;td&gt;（无条件执行）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;gt/lt&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;大于 / 小于&lt;/td&gt;&lt;td&gt;&lt;code&gt;subgt ...&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&amp;gt;&lt;/code&gt; 或 &lt;code&gt;&amp;lt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2. 寄存器模型&lt;a href=&quot;#2-寄存器模型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在执行指令前，必须知道指令操作的对象。I.MX6ULL 是 Load/Store 架构。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;原则：ALU（算术逻辑单元）只能处理&lt;strong&gt;通用寄存器&lt;/strong&gt;中的数据。&lt;/li&gt;
&lt;li&gt;即不能对内存里的变量做运算，必须：搬运到寄存器 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 计算 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 搬回内存。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;I.MX6ULL 有 16 个核心寄存器（R0-R15）：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;R0-R12：通用寄存器。&lt;/li&gt;
&lt;li&gt;R13（SP）：栈指针（当前的内存栈顶）。&lt;/li&gt;
&lt;li&gt;R14（LR）：链接寄存器（保存函数调用的返回地址）。&lt;/li&gt;
&lt;li&gt;R15（PC）：程序计数器（指向当前正在取指的地址）。&lt;strong&gt;修改 PC 就是跳转&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;在 ARM 开发中，寄存器最重要的属性是它的 AAPCS 角色（ARM Architecture Procedure Call Standard）。这决定了编译器如何使用它们。&lt;/p&gt;



























































































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;寄存器&lt;/th&gt;&lt;th&gt;别名&lt;/th&gt;&lt;th&gt;硬件功能&lt;/th&gt;&lt;th&gt;AAPCS&lt;/th&gt;&lt;th&gt;谁负责保存？&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;R0&lt;/td&gt;&lt;td&gt;a1&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;参数 1 / 返回值&lt;/td&gt;&lt;td&gt;调用者（Caller）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R1&lt;/td&gt;&lt;td&gt;a2&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;参数 2 / 返回值（64 位时）&lt;/td&gt;&lt;td&gt;调用者（Caller）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R2&lt;/td&gt;&lt;td&gt;a3&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;参数 3&lt;/td&gt;&lt;td&gt;调用者（Caller）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R3&lt;/td&gt;&lt;td&gt;a4&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;参数 4&lt;/td&gt;&lt;td&gt;调用者（Caller）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R4&lt;/td&gt;&lt;td&gt;v1&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;局部变量&lt;/td&gt;&lt;td&gt;被调用者（Callee）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R5&lt;/td&gt;&lt;td&gt;v2&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;局部变量&lt;/td&gt;&lt;td&gt;被调用者（Callee）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R6&lt;/td&gt;&lt;td&gt;v3&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;局部变量&lt;/td&gt;&lt;td&gt;被调用者（Callee）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R7&lt;/td&gt;&lt;td&gt;v4&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;局部变量（在 Thumb 模式下常用做栈帧指针）&lt;/td&gt;&lt;td&gt;被调用者（Callee）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R8&lt;/td&gt;&lt;td&gt;v5&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;局部变量&lt;/td&gt;&lt;td&gt;被调用者（Callee）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R9&lt;/td&gt;&lt;td&gt;v6/SB&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;局部变量（静态基址，平台相关）&lt;/td&gt;&lt;td&gt;被调用者（Callee）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R10&lt;/td&gt;&lt;td&gt;v7&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;局部变量&lt;/td&gt;&lt;td&gt;被调用者（Callee）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R11&lt;/td&gt;&lt;td&gt;v8/FP&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;帧指针（Frame Pointer）&lt;/td&gt;&lt;td&gt;被调用者（Callee）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R12&lt;/td&gt;&lt;td&gt;IP&lt;/td&gt;&lt;td&gt;通用&lt;/td&gt;&lt;td&gt;过程调用中间寄存器（Intra-Procedure-call scratch）&lt;/td&gt;&lt;td&gt;调用者（Caller）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R13&lt;/td&gt;&lt;td&gt;SP&lt;/td&gt;&lt;td&gt;栈指针&lt;/td&gt;&lt;td&gt;指向当前栈顶（Stach Pointer）&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R14&lt;/td&gt;&lt;td&gt;LR&lt;/td&gt;&lt;td&gt;链接寄存器&lt;/td&gt;&lt;td&gt;保存函数返回地址（Link Register）&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;R15&lt;/td&gt;&lt;td&gt;PC&lt;/td&gt;&lt;td&gt;程序计数器&lt;/td&gt;&lt;td&gt;指向取指地址（Program Counter）&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;除此之外还有两个非常重要的特殊状态寄存器 CPSR 和 SPSR。&lt;/p&gt;






















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;寄存器&lt;/th&gt;&lt;th&gt;全称&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;关键位域&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;CPSR&lt;/td&gt;&lt;td&gt;Current Program Status Register&lt;/td&gt;&lt;td&gt;当前程序状态寄存器&lt;/td&gt;&lt;td&gt;N, Z, C, V（条件标志位）&lt;br /&gt;I, F（中断屏蔽位）&lt;br /&gt;Mode（运行模式位：User, SVC, IRQ 等）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SPSR&lt;/td&gt;&lt;td&gt;Saved Program Status Register&lt;/td&gt;&lt;td&gt;备份程序状态寄存器&lt;/td&gt;&lt;td&gt;仅在异常模式下存在&lt;br /&gt;当中断发生时，硬件自动把 CPSR 里的值拷贝到 SPSR&lt;br /&gt;当中断返回时，再从 SPSR 恢复到 CPSR&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;ARMv7-A 实际上不止上述的 18 个寄存器，还有&lt;strong&gt;影子寄存器&lt;/strong&gt;（Banked Registers）。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;2.1 为什么要区分调用者保存和被调用者保存？&lt;a href=&quot;#21-为什么要区分调用者保存和被调用者保存&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;简单来说，这是 ARM 官方制定的规范，叫做 AAPCS（Procedure Call Standard for the ARM Architecture，ARM 架构过程调用标准）。ARM 公司不仅设计 CPU 硬件架构，还定义了软件应如何与这些硬件交互的 ABI（Application Binary Interface，应用程序二进制接口）。&lt;/p&gt;&lt;p&gt;制定这一规范的核心目的在于&lt;strong&gt;统一标准&lt;/strong&gt;。它确保了二进制层面的兼容性——无论代码是使用 GCC 还是 ARM Compiler 编译的，只要遵循 AAPCS，它们就能无缝协作。你无需担心不同编译器在参数传递或堆栈管理上的差异，从而可以放心地调用第三方提供的预编译库。&lt;/p&gt;&lt;p&gt;当 Caller 调用 Callee 时，参数通过 R0-R3 传递。调用结束后，返回值通常保存在 R0 或 R0-R1 中。这就带来了一个问题：Callee 执行时不可避免地会使用寄存器，Caller 如何确保自己的数据不被覆盖？&lt;/p&gt;&lt;p&gt;AAPCS 为此制定了明确的规范：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;易失性寄存器&lt;/strong&gt;（Volatile Registers, R0-R3, R12）：AAPCS 规定 Callee 可以随意修改它们。因此，如果 Caller 在 R0-R3 中存放了重要数据且在调用后仍需要使用，Caller 必须自己负责在调用前将它们压栈保存（Caller-saved）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非易失性寄存器&lt;/strong&gt;（Non-volatile Registers, R4-R11）：AAPCS 规定 Callee 必须保证函数返回时，这些寄存器的值与进入函数时完全一致。如果 Callee 内部需要使用 R4-R11，Callee 必须负责先将其压栈保存，并在返回前恢复（Callee-saved）。这样 Caller 就可以放心地将关键数据放在这里，无需担心被篡改。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;上述规则解决了函数间的协作问题，但&lt;strong&gt;中断&lt;/strong&gt;（Interrupt）打破了这种默契。中断是异步突发的，主程序（Caller）根本不知道何时会被打断，因此无法预先保存 R0-R3 等易失性寄存器。&lt;/p&gt;&lt;p&gt;为了解决这个问题，ARM Cortex-M 架构引入了&lt;strong&gt;硬件压栈&lt;/strong&gt;（Hardware Stacking）机制。当 CPU 响应中断时，硬件会自动将 Caller 上下文（包括 R0-R3、R12、LR、PC 和 xPSR）。这一过程无需软件干预。&lt;/p&gt;&lt;p&gt;因此，进入中断服务函数（ISR）后，寄存器已经被硬件保护好了。ISR 此时只需像普通函数一样，遵守 AAPCS 规则处理 R4-R11（即如果用到则保存）即可。这种硬件机制极大降低了中断延迟，也简化了软件设计。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.2 R12（IP）是干什么的？&lt;a href=&quot;#22-r12ip是干什么的&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;它是一个被很多人容易忽略的寄存器，主要用于链接器（Linker）插入的代码。当你的程序非常大，&lt;code&gt;bl target&lt;/code&gt; 跳转的目标太远（超过 &lt;span&gt;&lt;span&gt;±32MB\pm 32\text{MB}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;±&lt;/span&gt;&lt;span&gt;32&lt;/span&gt;&lt;span&gt;&lt;span&gt;MB&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;）时，&lt;code&gt;bl&lt;/code&gt; 指令够不着。此时，链接器会自动插入一段小代码（Veneer/Trampoline），先跳转到 R12 指向的中转站，再由 R12 赋值给 PC 进行长跳转。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.3 R15（PC）为什么有 +8 现象？&lt;a href=&quot;#23-r15pc为什么有-8-现象&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在 ARM 状态（32 位指令）下，读取 PC 的值，通常是当前指令地址 + 8。这则是因为 CPU 的流水线（Pipeline）设计，当 CPU 执行当前指令时，取指单元已经在取下下条指令了（当前指令地址 +8 地址处）。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;3. 指令集讲解&lt;a href=&quot;#3-指令集讲解&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;3.1 数据处理指令（Data Processing）&lt;a href=&quot;#31-数据处理指令data-processing&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;核心逻辑&lt;/strong&gt;：ALU 运算，结果写回寄存器，并可选更新 CPSR（如果指令后加 &lt;code&gt;s&lt;/code&gt;，如 &lt;code&gt;adds&lt;/code&gt;）&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;section&gt;&lt;h4&gt;3.1.1 算术类&lt;a href=&quot;#311-算术类&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;汇编举例&lt;/th&gt;&lt;th&gt;C 语言映射&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;add&lt;/code&gt;&lt;br /&gt;(Add)&lt;/td&gt;&lt;td&gt;加法。&lt;/td&gt;&lt;td&gt;&lt;code&gt;add r0, r1, r2&lt;/code&gt;&lt;br /&gt;(&lt;code&gt;R0 = R1 + R2&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;&lt;code&gt;a = b + c;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;sub&lt;/code&gt;&lt;br /&gt;(Subtract)&lt;/td&gt;&lt;td&gt;减法。&lt;/td&gt;&lt;td&gt;&lt;code&gt;sub r0, r1, #1&lt;/code&gt;&lt;br /&gt;(&lt;code&gt;R0 = R1 - 1&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;&lt;code&gt;a = b - 1;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;rsb&lt;/code&gt;&lt;br /&gt;(Reverse Substract)&lt;/td&gt;&lt;td&gt;逆向减法。&lt;br /&gt;用于 R0 = 常数 - Rx 的场景。&lt;/td&gt;&lt;td&gt;&lt;code&gt;rsb r0, r1, #0&lt;/code&gt;&lt;br /&gt;(&lt;code&gt;R0 = 0 - R1&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;&lt;code&gt;a = -b;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;adc&lt;/code&gt;&lt;br /&gt;(Add with Carry)&lt;/td&gt;&lt;td&gt;带进位加法。&lt;br /&gt;用于实现 64 位加法的高 32 位运算。&lt;/td&gt;&lt;td&gt;&lt;code&gt;adc r0, r1, r2&lt;/code&gt;&lt;br /&gt;(&lt;code&gt;R0 = R1 + R2 + Carry&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;&lt;code&gt;(long long )a + (long long)b&lt;/code&gt; 的高位部分&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;sbc&lt;/code&gt;&lt;br /&gt;(Subtract with Carry)&lt;/td&gt;&lt;td&gt;带借位减法。&lt;br /&gt;用于 64 位减法。&lt;/td&gt;&lt;td&gt;&lt;code&gt;sbc r0, r1, r2&lt;/code&gt;&lt;br /&gt;(&lt;code&gt;R0 = R1 - R2 - !Carry&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;64 位减法的高位部分&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;mul&lt;/code&gt;&lt;br /&gt;(Multiply)&lt;/td&gt;&lt;td&gt;32 位乘法（结果取低 32 位）&lt;/td&gt;&lt;td&gt;&lt;code&gt;mul r0, r1, r2&lt;/code&gt;&lt;br /&gt;(&lt;code&gt;R0 = R1 * R2&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;&lt;code&gt;int a = b * c;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;mla&lt;/code&gt;&lt;br /&gt;(Multiply Accumulate)&lt;/td&gt;&lt;td&gt;乘法运算。&lt;br /&gt;DSP 算法核心指令。&lt;/td&gt;&lt;td&gt;&lt;code&gt;mul r0, r1, r2, r3&lt;/code&gt;&lt;br /&gt;(&lt;code&gt;R0 = R1 * R2 +  R3&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;&lt;code&gt;a = (b * c) + d;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;udiv&lt;/code&gt;&lt;br /&gt;(Unsigned Divide)&lt;/td&gt;&lt;td&gt;无符号除法。&lt;/td&gt;&lt;td&gt;&lt;code&gt;udiv r0, r1, r2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;unsigned a = b / c;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;sdiv&lt;/code&gt;&lt;br /&gt;(Signed Divided)&lt;/td&gt;&lt;td&gt;有符号除法。&lt;/td&gt;&lt;td&gt;&lt;code&gt;sdiv r0, r1, r2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;int a = b / c;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;3.1.2 逻辑类&lt;a href=&quot;#312-逻辑类&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;汇编举例&lt;/th&gt;&lt;th&gt;C 语言映射&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;and&lt;/code&gt;&lt;br /&gt;(Logical AND)&lt;/td&gt;&lt;td&gt;按位与。&lt;br /&gt;常用于&lt;strong&gt;提取&lt;/strong&gt;某几位。&lt;/td&gt;&lt;td&gt;&lt;code&gt;and r0, r1, #0xff&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;a = b &amp;amp; 0xff;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;orr&lt;/code&gt;&lt;br /&gt;(Logical OR)&lt;/td&gt;&lt;td&gt;按位或。&lt;br /&gt;常用于&lt;strong&gt;置位&lt;/strong&gt;（设为 1）。&lt;/td&gt;&lt;td&gt;&lt;code&gt;orr r0, r0, #(1&amp;lt;&amp;lt;3)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;`r0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;eor&lt;/code&gt;&lt;br /&gt;(Exclusive OR)&lt;/td&gt;&lt;td&gt;按位异或。&lt;br /&gt;常用于&lt;strong&gt;翻转&lt;/strong&gt;某几位。&lt;/td&gt;&lt;td&gt;&lt;code&gt;eor r0, r0, #1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;a = b ^ 1;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;bic&lt;/code&gt;&lt;br /&gt;(Bit Clear)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;位清除&lt;/strong&gt;。&lt;br /&gt;&lt;code&gt;and&lt;/code&gt; 的反向操作：&lt;code&gt;dest = op1 and (not op2)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;bic r0, r0, #(1&amp;lt;&amp;lt;3)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;r0 &amp;amp;= ~(1 &amp;lt;&amp;lt; 3);&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;3.1.3 数据搬运&lt;a href=&quot;#313-数据搬运&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;






















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;汇编举例&lt;/th&gt;&lt;th&gt;C 语言映射&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;mov&lt;/code&gt;&lt;br /&gt;(Move)&lt;/td&gt;&lt;td&gt;寄存器间传值，或立即数赋值。&lt;/td&gt;&lt;td&gt;&lt;code&gt;mov r0, r1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;a = b;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;mvn&lt;/code&gt;&lt;br /&gt;(Move Not)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;按位取反&lt;/strong&gt;后传送。&lt;/td&gt;&lt;td&gt;&lt;code&gt;mvn r0, #0&lt;/code&gt;&lt;br /&gt;(&lt;code&gt;r0 = 0xffffffff&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;&lt;code&gt;a = ~0;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;ARM 指令是定长 32 位的。除去操作码和条件码，留给立即数的空间只有 12 位。并且这 12 位还不是直接存数字，而是被拆分位 8 位常数 + 4 位循环右移。&lt;/p&gt;&lt;p&gt;这就意味着在向寄存器写入某些立即数时，例如 &lt;code&gt;mov r0, #0x12345678&lt;/code&gt; 是会失败的，因为 &lt;code&gt;0x12345678&lt;/code&gt; 这个立即数太乱了，无法通过 8 位常数 + 4 位循环右移凑出来。&lt;/p&gt;&lt;p&gt;解决办法是使用 &lt;code&gt;ldr r0, =0x12345678&lt;/code&gt;。此时，汇编器会悄悄在代码段的末尾（Literal Pool）找个地方放 &lt;code&gt;0x12345678&lt;/code&gt;，然后把这条指令翻译成去那个地址读数据。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;3.1.4 比较指令&lt;a href=&quot;#314-比较指令&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;不保存运算结果&lt;/strong&gt;，只更新 CPSR 的标志位（N, Z, C, V），为后面的条件执行做铺垫。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;汇编举例&lt;/th&gt;&lt;th&gt;C 语言映射&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;cmp&lt;/code&gt;&lt;br /&gt;(Compare)&lt;/td&gt;&lt;td&gt;本质是 &lt;code&gt;sub&lt;/code&gt;。&lt;br /&gt;比较大小。&lt;/td&gt;&lt;td&gt;&lt;code&gt;cmp r0, #5&lt;/code&gt;&lt;br /&gt;(&lt;code&gt;r0 - 5&lt;/code&gt;，更新 Z 标志位)&lt;/td&gt;&lt;td&gt;&lt;code&gt;if (a &amp;gt;= 5)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;tst&lt;/code&gt;&lt;br /&gt;(Test)&lt;/td&gt;&lt;td&gt;本质是 &lt;code&gt;and&lt;/code&gt;。&lt;br /&gt;测试某位是否为 1。&lt;/td&gt;&lt;td&gt;&lt;code&gt;tst r0, #(1&amp;lt;&amp;lt;3)&lt;/code&gt;&lt;br /&gt;(&lt;code&gt;r0 &amp;amp; 0x8&lt;/code&gt;，更新 Z 标志位)&lt;/td&gt;&lt;td&gt;&lt;code&gt;if (a &amp;amp; (1&amp;lt;&amp;lt;3))&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;teq&lt;/code&gt;&lt;br /&gt;(Test Equivalence)&lt;/td&gt;&lt;td&gt;本质是 &lt;code&gt;eor&lt;/code&gt;。&lt;br /&gt;测试两个数是否相等（不影响进位标志）。&lt;/td&gt;&lt;td&gt;&lt;code&gt;teq r0, r1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;常用于汇编手写检查&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;3.1.5 桶形移位器（Barrel Shifter）&lt;a href=&quot;#315-桶形移位器barrel-shifter&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;在 ARM 中，移位操作不是独立指令，而是数据进入 ALU 之前的&lt;strong&gt;预处理&lt;/strong&gt;。它是免费的。指令 &lt;code&gt;add r0, r1, r2, lsl #2&lt;/code&gt; 的物理含义是：&lt;/p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;R0=R1+(R2≪2)\text{R0} = \text{R1} + (\text{R2} \ll 2)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;R0&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;R1&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;R2&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;≪&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;p&gt;这在单周期内完成，这就是为什么 ARM 处理数组索引（地址 = 基址 + 索引 &lt;span&gt;&lt;span&gt;×\times&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;×&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 4）特别快。&lt;/p&gt;




















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;模式&lt;/th&gt;&lt;th&gt;物理动作&lt;/th&gt;&lt;th&gt;C 标志位更新&lt;/th&gt;&lt;th&gt;数学含义&lt;/th&gt;&lt;th&gt;C 语言映射&lt;/th&gt;&lt;th&gt;典型应用场景&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;lsl&lt;/code&gt;&lt;br /&gt;(Logical Shift Left)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;左移&lt;/strong&gt;。&lt;br /&gt;低位补 0，高位移出&lt;/td&gt;&lt;td&gt;最后移出的位&lt;/td&gt;&lt;td&gt;&lt;span&gt;&lt;span&gt;×2n\times 2^n&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;×&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;x &amp;lt;&amp;lt; n&lt;/code&gt;&lt;/td&gt;&lt;td&gt;乘法优化、位字段对齐、生成掩码&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;lsr&lt;/code&gt;&lt;br /&gt;(Logical Shift Right)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;右移&lt;/strong&gt;。&lt;br /&gt;高位补 0，低位移出&lt;/td&gt;&lt;td&gt;最后移出的位&lt;/td&gt;&lt;td&gt;&lt;span&gt;&lt;span&gt;/2n/2^n&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;（无符号）&lt;/td&gt;&lt;td&gt;&lt;code&gt;(unsigned)x &amp;gt;&amp;gt; n&lt;/code&gt;&lt;/td&gt;&lt;td&gt;无符号数除法、读取特定位段&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;asr&lt;/code&gt;&lt;br /&gt;(Arithmetic Shift Right)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;算术右移&lt;/strong&gt;。&lt;br /&gt;高位补&lt;strong&gt;符号位&lt;/strong&gt;，低位移出&lt;/td&gt;&lt;td&gt;最后移出的位&lt;/td&gt;&lt;td&gt;&lt;span&gt;&lt;span&gt;/2n/2^n&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;（有符号）&lt;/td&gt;&lt;td&gt;&lt;code&gt;(signed)x &amp;gt;&amp;gt; n&lt;/code&gt;&lt;/td&gt;&lt;td&gt;有符号数除法&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ror&lt;/code&gt;&lt;br /&gt;(Rotate Right)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;循环右移&lt;/strong&gt;。&lt;br /&gt;低位移出后，&lt;strong&gt;填补到高位&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;最后移出的位&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;`(x &amp;gt;&amp;gt; n)&lt;/td&gt;&lt;td&gt;(x &amp;lt;&amp;lt; (32-n))`&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;rrx&lt;/code&gt;&lt;br /&gt;(Rotate Right with Extend)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;带扩展循环右移&lt;/strong&gt;。&lt;br /&gt;33 位循环&lt;br /&gt;（32 位寄存器 + 1 位 Carry）&lt;/td&gt;&lt;td&gt;寄存器移出的 LSB&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;无&lt;/td&gt;&lt;td&gt;大数运算、多精度算术&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.2 内存访问指令（Memory Access）&lt;a href=&quot;#32-内存访问指令memory-access&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是 CPU 与外部世界（DDR 内存、GPIO 控制器等）交互的唯一桥梁。&lt;/p&gt;&lt;section&gt;&lt;h4&gt;3.2.1 单寄存器&lt;a href=&quot;#321-单寄存器&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;














































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;汇编举例&lt;/th&gt;&lt;th&gt;C 语言映射&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ldr&lt;/code&gt;&lt;br /&gt;(Load Register)&lt;/td&gt;&lt;td&gt;从内存读 4 字节到寄存器。&lt;/td&gt;&lt;td&gt;&lt;code&gt;ldr r0, [r1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;r0 = *r1;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;br /&gt;(Store Register)&lt;/td&gt;&lt;td&gt;把寄存器的值写入内存 4 字节。&lt;/td&gt;&lt;td&gt;&lt;code&gt;str r0, [r1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;*r1 = r0;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ldrb&lt;/code&gt; (8 bit)&lt;br /&gt;(Load Byte)&lt;/td&gt;&lt;td&gt;读 1 字节，高 24 位&lt;strong&gt;补零&lt;/strong&gt;。&lt;/td&gt;&lt;td&gt;&lt;code&gt;ldrb r0, [r1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;r0 = (unsigned char)*r1;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ldrh&lt;/code&gt; (16 bit)&lt;br /&gt;(Load Halfword)&lt;/td&gt;&lt;td&gt;读 2 字节，高 16 位&lt;strong&gt;补零&lt;/strong&gt;。&lt;/td&gt;&lt;td&gt;&lt;code&gt;ldrh r0, [r1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;r0 = (unsigned short)*r1;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ldrsb&lt;/code&gt;&lt;br /&gt;(Load Signed Byte)&lt;/td&gt;&lt;td&gt;读 1 字节，高 24 位&lt;strong&gt;补符号位&lt;/strong&gt;。&lt;/td&gt;&lt;td&gt;&lt;code&gt;ldrsb r0, [r1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;r0 = (char)*r1;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ldrsh&lt;/code&gt;&lt;br /&gt;(Load Signed Halfword)&lt;/td&gt;&lt;td&gt;读 2 字节，高 16 位&lt;strong&gt;补符号位&lt;/strong&gt;。&lt;/td&gt;&lt;td&gt;&lt;code&gt;ldrsh r0, [r1]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;r0 = (short)*r1;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;3.2.2 寻址模式&lt;a href=&quot;#322-寻址模式&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;寻址模式&lt;/th&gt;&lt;th&gt;汇编举例&lt;/th&gt;&lt;th&gt;C 语言映射&lt;/th&gt;&lt;th&gt;状态&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;立即数偏移&lt;br /&gt;（Offset）&lt;/td&gt;&lt;td&gt;&lt;code&gt;ldr r0, [r1, #4]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;r0 = *(r1 + 1);&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;r1&lt;/code&gt; 保持不变&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;前索引&lt;br /&gt;（Pre-indexed）&lt;/td&gt;&lt;td&gt;&lt;code&gt;ldr r0, [r1, #4]!&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;r0 = *(++ptr);&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;r1&lt;/code&gt; 在访问内存&lt;strong&gt;前&lt;/strong&gt;更新为 &lt;code&gt;r1 + 4&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;后索引&lt;br /&gt;（Post-indexed）&lt;/td&gt;&lt;td&gt;&lt;code&gt;ldr r0, [r1], #4&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;r0 = *(ptr++);&lt;/code&gt;&lt;/td&gt;&lt;td&gt;访问内存&lt;strong&gt;后&lt;/strong&gt;，&lt;code&gt;r1&lt;/code&gt; 更新为 &lt;code&gt;r1 + 4&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;3.2.3 多寄存器&lt;a href=&quot;#323-多寄存器&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;这是 ARM 实现&lt;strong&gt;栈&lt;/strong&gt;（Stack）和&lt;strong&gt;上下文切换&lt;/strong&gt;（Context Switch）的基石。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;






















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;汇编举例&lt;/th&gt;&lt;th&gt;场景&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ldm&lt;/code&gt;&lt;br /&gt;(Load Multiple)&lt;/td&gt;&lt;td&gt;内存 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 多个寄存器。&lt;/td&gt;&lt;td&gt;&lt;code&gt;ldm r0, {r4-r6, pc}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;恢复现场并返回&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;stm&lt;/code&gt;&lt;br /&gt;(Store Multiple)&lt;/td&gt;&lt;td&gt;多个寄存器 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 内存。&lt;/td&gt;&lt;td&gt;&lt;code&gt;stm r0, {r4-r6, lr}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;保存现场&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;说明：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ldm r0, {r4-r6, pc}&lt;/code&gt;：从以 &lt;code&gt;r0&lt;/code&gt; 为基址的内存地址开始，依次加载数据到寄存器 &lt;code&gt;r4&lt;/code&gt;、&lt;code&gt;r5&lt;/code&gt;、&lt;code&gt;r6&lt;/code&gt; 和 &lt;code&gt;pc&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stm r0, {r4-r6, lr}&lt;/code&gt;：从寄存器 &lt;code&gt;r4&lt;/code&gt;、&lt;code&gt;r5&lt;/code&gt;、&lt;code&gt;r6&lt;/code&gt; 和 &lt;code&gt;lr&lt;/code&gt; 中，依次将数据存储到以 &lt;code&gt;r0&lt;/code&gt; 为基址的内存地址。&lt;/li&gt;
&lt;/ul&gt;&lt;section&gt;&lt;h5&gt;后缀模式&lt;a href=&quot;#后缀模式&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;&lt;p&gt;虽然有 &lt;code&gt;ia&lt;/code&gt;、&lt;code&gt;ib&lt;/code&gt;、&lt;code&gt;da&lt;/code&gt; 和 &lt;code&gt;db&lt;/code&gt; 四种，但 ARM 官方推荐在操作栈时使用别名，方便记忆：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;stmfd&lt;/code&gt; (Full Descending) = &lt;code&gt;stmdb&lt;/code&gt; (Decrement Before)：入栈（压栈）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ldmfd&lt;/code&gt; (Full Descending) = &lt;code&gt;ldmia&lt;/code&gt; (Increment After)：出栈。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;push {reglist}&lt;/code&gt; 等价于 &lt;code&gt;stmdb sp!, {reglist}&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pop {reglist}&lt;/code&gt; 等价于 &lt;code&gt;ldmia sp!, {reglist}&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.3 分支与控制流（Branch &amp;amp; Control）&lt;a href=&quot;#33-分支与控制流branch--control&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;打破顺序执行，实现 &lt;code&gt;if&lt;/code&gt;、&lt;code&gt;for&lt;/code&gt;、&lt;code&gt;function call&lt;/code&gt;。&lt;/p&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;汇编举例&lt;/th&gt;&lt;th&gt;C 语言映射&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;b&lt;/code&gt;&lt;br /&gt;(Branch)&lt;/td&gt;&lt;td&gt;相对跳转（范围 &lt;span&gt;&lt;span&gt;±32MB\pm 32\text{MB}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;±&lt;/span&gt;&lt;span&gt;32&lt;/span&gt;&lt;span&gt;&lt;span&gt;MB&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;）&lt;/td&gt;&lt;td&gt;&lt;code&gt;b label&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;goto label;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;bl&lt;/code&gt;&lt;br /&gt;(Branch with Link)&lt;/td&gt;&lt;td&gt;带连接跳转。&lt;br /&gt;&lt;strong&gt;保存下一条指令地址到 &lt;code&gt;lr&lt;/code&gt;&lt;/strong&gt;。&lt;/td&gt;&lt;td&gt;&lt;code&gt;bl func&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;func();&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;bx&lt;/code&gt;&lt;br /&gt;(Branch Exchange)&lt;/td&gt;&lt;td&gt;跳转并根据目标地址最低位切换状态&lt;br /&gt;（ARM &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; Thumb）。&lt;/td&gt;&lt;td&gt;&lt;code&gt;bx lr&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;return;&lt;/code&gt;（函数返回）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;blx&lt;/code&gt;&lt;br /&gt;(Branch Link Exchange)&lt;/td&gt;&lt;td&gt;&lt;code&gt;bl&lt;/code&gt; + &lt;code&gt;bx&lt;/code&gt; 的结合。&lt;br /&gt;调用不同指令集的函数。&lt;/td&gt;&lt;td&gt;&lt;code&gt;blx r0&lt;/code&gt;&lt;/td&gt;&lt;td&gt;函数指针调用&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;还可以直接修改 PC 寄存器实现跳转，例如 &lt;code&gt;mov pc, lr&lt;/code&gt;，这是函数返回的底层实现。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.4 系统与状态指令（System &amp;amp; Status）&lt;a href=&quot;#34-系统与状态指令system--status&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这部分在使用上层语言实现业务逻辑时几乎看不到，但是对于操作系统而言至关重要。&lt;/p&gt;&lt;section&gt;&lt;h4&gt;3.4.1 状态寄存器访问&lt;a href=&quot;#341-状态寄存器访问&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;






















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;汇编举例&lt;/th&gt;&lt;th&gt;场景&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;mrs&lt;/code&gt;&lt;br /&gt;(Move Register from Status)&lt;/td&gt;&lt;td&gt;读取 CPSR/SPSR 到通用寄存器。&lt;/td&gt;&lt;td&gt;&lt;code&gt;mrs r0, cpsr&lt;/code&gt;&lt;/td&gt;&lt;td&gt;读取当前中断状态&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;msr&lt;/code&gt;&lt;br /&gt;(Move Status from Register)&lt;/td&gt;&lt;td&gt;将通用寄存器写会 CPSR/SPSR。&lt;/td&gt;&lt;td&gt;&lt;code&gt;msr cpsr_c, r0&lt;/code&gt;&lt;/td&gt;&lt;td&gt;开/关中断&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;3.4.2 异常与中断生成&lt;a href=&quot;#342-异常与中断生成&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;



















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;场景&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;svc&lt;/code&gt;&lt;br /&gt;(Supervisor Call)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;软中断&lt;/strong&gt;。&lt;br /&gt;CPU 进入 SVC 模式。&lt;br /&gt;Linux 中用于从用户态（User）陷入内核态（Kernel）。&lt;/td&gt;&lt;td&gt;系统调用（&lt;code&gt;open&lt;/code&gt;，&lt;code&gt;read&lt;/code&gt; 等）。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;bkpt&lt;/code&gt;&lt;br /&gt;(Breakpoint)&lt;/td&gt;&lt;td&gt;断点指令。&lt;br /&gt;执行到此时 CPU 暂停并通知调试器。&lt;/td&gt;&lt;td&gt;JTAG/GDB 调试。&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;3.4.3 协处理器指令&lt;a href=&quot;#343-协处理器指令&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;I.MX6ULL 中，CP15 协处理器控制着 MMU、Cache 和中断向量表基地址。&lt;/p&gt;



















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;助记法&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;mcr&lt;/code&gt;&lt;br /&gt;(Move to Coprocessor from Register)&lt;/td&gt;&lt;td&gt;CPU &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; CP15&lt;br /&gt;（写配置）&lt;/td&gt;&lt;td&gt;R（Register）在最后，表示源&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;mrc&lt;/code&gt;&lt;br /&gt;(Move to Register from Coprocessor)&lt;/td&gt;&lt;td&gt;CP15 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; CPU&lt;br /&gt;（读状态）&lt;/td&gt;&lt;td&gt;C（Coprocessor）在最后，表示源&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;例如：&lt;code&gt;mcr p15, 0, r0, c1, c0, 0&lt;/code&gt;（开启 MMU）。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;协处理器&lt;/strong&gt;（Coprocessor）时依附于 CPU 主核的独立电路单元。ARM 主核（ALU）只管算逻辑和搬砖，像 MMU（内存映射管理）、Cache 配置这些脏活累活，都丢给 CP15 协处理器；浮点运算丢给 CP10/CP11。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;3.4.4 内存屏障（Memory Barriers）&lt;a href=&quot;#344-内存屏障memory-barriers&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;场景&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;dmb&lt;/code&gt;&lt;br /&gt;(Data Memory Barrier)&lt;/td&gt;&lt;td&gt;确保指令前的内存访问在指令后的内存访问之前完成。&lt;/td&gt;&lt;td&gt;操作 DMA 描述符时&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;dsb&lt;/code&gt;&lt;br /&gt;(Data Synchronization Barrier)&lt;/td&gt;&lt;td&gt;比 DMB 更强。&lt;br /&gt;确保前面的访存全部完成，CPU 才会执行下一条指令。&lt;/td&gt;&lt;td&gt;修改页表后&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;isb&lt;/code&gt;&lt;br /&gt;(Instruction Synchronization Barrier)&lt;/td&gt;&lt;td&gt;冲刷流水线。确保重新取指。&lt;/td&gt;&lt;td&gt;修改了代码段或中断向量表后&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.5 同步与原子操作（Synchronization）&lt;a href=&quot;#35-同步与原子操作synchronization&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;对于多任务操作系统，需要处理竞态条件。&lt;/p&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指令&lt;/th&gt;&lt;th&gt;解释&lt;/th&gt;&lt;th&gt;C 语言映射&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ldrex&lt;/code&gt;&lt;br /&gt;(Load Register Exclusive)&lt;/td&gt;&lt;td&gt;读取并设置独占监视器&lt;/td&gt;&lt;td&gt;&lt;code&gt;atomic_load&lt;/code&gt; (C11)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;strex&lt;/code&gt;&lt;br /&gt;(Store Register Exclusive)&lt;/td&gt;&lt;td&gt;如果监视状态未被破坏，则写入。&lt;br /&gt;返回 0 成功，1 失败。&lt;/td&gt;&lt;td&gt;&lt;code&gt;atomic_compare_exchange&lt;/code&gt; (C11)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;clrex&lt;/code&gt;&lt;br /&gt;(Clear Exclusive)&lt;/td&gt;&lt;td&gt;清除监视器状态。&lt;/td&gt;&lt;td&gt;上下文切换时清理残余状态&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;ARM 没有 x86 的 &lt;code&gt;lock&lt;/code&gt; 总线锁，它使用 “&lt;strong&gt;独占监视器&lt;/strong&gt;” 硬件。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ldrex&lt;/code&gt; 读取数据的同时标记该地址为 “被我监视”。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strex&lt;/code&gt; 尝试写入。如果期间没有其他核心 / DMA 修改过该地址，写入成功；否则写入失败。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这会实现 &lt;code&gt;mutex&lt;/code&gt;（互斥锁）和 &lt;code&gt;semaphore&lt;/code&gt;（信号量）的基石。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;互斥锁（Mutex）&amp;amp; 信号量（Semaphore）：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;互斥锁（厕所钥匙）：只有一个资源（厕所）。你拿了钥匙进去锁门，别惹只能在门口排队（阻塞），直到你出来还钥匙。用户保护&lt;strong&gt;临界区&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;信号量（餐厅叫号）：有 &lt;span&gt;&lt;span&gt;NN&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 个资源（空桌子）。来一个人 &lt;span&gt;&lt;span&gt;N−1N-1&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;−&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，走一个人 &lt;span&gt;&lt;span&gt;N+1N+1&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。当 &lt;span&gt;&lt;span&gt;N=0N=0&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 时，新来的人必须等到。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这两个都是软件概念，但底层必须由&lt;strong&gt;原子指令&lt;/strong&gt;支持。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>如何将 WSL2 镜像无损迁移至非系统盘</title><link>https://bfmhno3.github.io/posts/wsl2-image-migration-guide/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/wsl2-image-migration-guide/</guid><description>详细记录在 Windows 11 环境下，将默认安装在 C 盘的 WSL2 Ubuntu 镜像无损迁移至其他数据盘的完整过程，包含导出、导入、默认用户配置及原实例清理，有效释放系统盘空间。</description><pubDate>Sat, 14 Mar 2026 05:16:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;1 引言&lt;a href=&quot;#1-引言&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;随着开发环境的日益庞大，默认安装在 &lt;code&gt;C:\&lt;/code&gt; 盘的 WSL2 (Windows Subsystem for Linux) 镜像体积也会不断膨胀。本文将分享如何通过命令行操作，将 WSL 迁移至 &lt;code&gt;E:\&lt;/code&gt; 盘乃至其他盘符，彻底释放系统盘空间。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;1.1 笔者环境&lt;a href=&quot;#11-笔者环境&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;名称&lt;/th&gt;&lt;th&gt;版本&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Windows 11 Pro&lt;/td&gt;&lt;td&gt;25H2 26200.8037&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;PWSH&lt;/td&gt;&lt;td&gt;7.5.5&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;WSL&lt;/td&gt;&lt;td&gt;2.6.3.0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ubuntu&lt;/td&gt;&lt;td&gt;22.04 LTS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ubuntu&lt;/td&gt;&lt;td&gt;24.04 LTS&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：这里的 PWSH 并不是指 Windows 系统内置的经典 PowerShell，而是跨平台的开源项目 &lt;a href=&quot;https://github.com/PowerShell/PowerShell&quot; target=&quot;_blank&quot;&gt;PowerShell Core&lt;/a&gt;。下文中使用 PowerShell 指代系统默认版本，PWSH 指代开源版本。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2 准备工作&lt;a href=&quot;#2-准备工作&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;2.1 确认当前安装状态&lt;a href=&quot;#21-确认当前安装状态&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;如果你还没有安装镜像，或想查看当前支持的发行版，可以使用以下命令：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 查看支持的在线发行版&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--list&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--online&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 安装 Ubuntu 镜像&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Ubuntu-22.04&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Ubuntu-24.04&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;在终端输入以下命令，查看当前已安装的发行版：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.1 确定原镜像及目标路径&lt;a href=&quot;#21-确定原镜像及目标路径&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;WSL2 的虚拟磁盘文件通常为 &lt;code&gt;ext4.vhdx&lt;/code&gt;。你可以使用强大的文件搜索工具 &lt;a href=&quot;https://www.voidtools.com/zh-cn/&quot; target=&quot;_blank&quot;&gt;Everything&lt;/a&gt; 来定位它们。不同版本的 Ubuntu 默认安装路径可能略有不同。&lt;/p&gt;
















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;镜像&lt;/th&gt;&lt;th&gt;原始路径&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Ubuntu 22.04 LTS&lt;/td&gt;&lt;td&gt;&lt;code&gt;C:\Users\&amp;lt;your_windows_username&amp;gt;\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu22.04LTS_...\LocalState&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ubuntu 24.04 LTS&lt;/td&gt;&lt;td&gt;&lt;code&gt;C:\Users\&amp;lt;your_windows_username&amp;gt;\AppData\wsl\{UUID}&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;可以看到这里两者的路径有着细微差别，Ubuntu 22.04 LTS 安装在 &lt;code&gt;Local\Packages&lt;/code&gt; 下，而 Ubuntu 24.04 LTS 却安装在 &lt;code&gt;Local&lt;/code&gt; 同级的 &lt;code&gt;wsl&lt;/code&gt; 下。&lt;/p&gt;&lt;p&gt;具体原因犹未可知，笔者暂时没有时间去查阅官方文档，但是并不影响后续步骤以及日常使用。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.2 建立新的位置&lt;a href=&quot;#22-建立新的位置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在上一步中已经获取到了原始的位置，根据笔者的风格，迁移之后的镜像路径应该尽可能维持原来的目录结构，但是笔者还是做了一点细微的修改，迁移路径具体如下：&lt;/p&gt;
















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;镜像&lt;/th&gt;&lt;th&gt;迁移路径&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Ubuntu 22.04 LTS&lt;/td&gt;&lt;td&gt;&lt;code&gt;E:\Users\&amp;lt;your_windows_username&amp;gt;\AppData\wsl\ubuntu2204&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ubuntu 24.04 LTS&lt;/td&gt;&lt;td&gt;&lt;code&gt;E:\Users\&amp;lt;your_windows_username&amp;gt;\AppData\wsl\ubuntu2404&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Warning&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;请在后续的命令行中将 &lt;code&gt;&amp;lt;your_windows_username&amp;gt;&lt;/code&gt; 替换为你的实际 Windows 用户名。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;在新版的 PWSH 中可以直接使用以下命令创建目录：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;mkdir &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;p E:\Users\&amp;lt;your_windows_username&amp;gt;\AppData\wsl\ubuntu2204\&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;mkdir &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;p E:\Users\&amp;lt;your_windows_username&amp;gt;\AppData\wsl\ubuntu2404\&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;或者以下命令（支持在 PowerShell 中使用）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;New-Item&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;Path E:\Users\&amp;lt;your_windows_username&amp;gt;\AppData\wsl\ubuntu2204\ &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;ItemType Directory&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;New-Item&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;Path E:\Users\&amp;lt;your_windows_username&amp;gt;\AppData\wsl\ubuntu2404\ &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;ItemType Directory&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;3 迁移镜像&lt;a href=&quot;#3-迁移镜像&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;3.1 导出原 WSL 分发&lt;a href=&quot;#31-导出原-wsl-分发&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在进行导出操作前，&lt;strong&gt;强烈建议先停止 WSL2 中的所有工作并彻底关闭所有正在运行的 WSL 实例&lt;/strong&gt;以防止数据损坏：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--shutdown&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;接着，将现有的系统状态导出为 &lt;code&gt;.tar&lt;/code&gt; 压缩包：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Ubuntu-22.04&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;E:&lt;/span&gt;&lt;span&gt;\U&lt;/span&gt;&lt;span&gt;sers&lt;/span&gt;&lt;span&gt;\&amp;lt;&lt;/span&gt;&lt;span&gt;your_windows_username&amp;gt;&lt;/span&gt;&lt;span&gt;\A&lt;/span&gt;&lt;span&gt;ppData&lt;/span&gt;&lt;span&gt;\w&lt;/span&gt;&lt;span&gt;sl&lt;/span&gt;&lt;span&gt;\u&lt;/span&gt;&lt;span&gt;buntu2204.tar&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Ubuntu-24.04&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;E:&lt;/span&gt;&lt;span&gt;\U&lt;/span&gt;&lt;span&gt;sers&lt;/span&gt;&lt;span&gt;\&amp;lt;&lt;/span&gt;&lt;span&gt;your_windows_username&amp;gt;&lt;/span&gt;&lt;span&gt;\A&lt;/span&gt;&lt;span&gt;ppData&lt;/span&gt;&lt;span&gt;\w&lt;/span&gt;&lt;span&gt;sl&lt;/span&gt;&lt;span&gt;\u&lt;/span&gt;&lt;span&gt;buntu2404.tar&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.2 将压缩包导入新位置&lt;a href=&quot;#32-将压缩包导入新位置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;使用 &lt;code&gt;wsl --import&lt;/code&gt; 命令将刚才导出的 &lt;code&gt;.tar&lt;/code&gt; 文件导入到我们在 E 盘创建的新目录中。为了方便区分，我们将导入后的新实例命名为小写的 &lt;code&gt;ubuntu2204&lt;/code&gt; 和 &lt;code&gt;ubuntu240&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ubuntu2204&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;E:&lt;/span&gt;&lt;span&gt;\U&lt;/span&gt;&lt;span&gt;sers&lt;/span&gt;&lt;span&gt;\&amp;lt;&lt;/span&gt;&lt;span&gt;your_windows_username&amp;gt;&lt;/span&gt;&lt;span&gt;\A&lt;/span&gt;&lt;span&gt;ppData&lt;/span&gt;&lt;span&gt;\w&lt;/span&gt;&lt;span&gt;sl&lt;/span&gt;&lt;span&gt;\u&lt;/span&gt;&lt;span&gt;buntu2204&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;E:&lt;/span&gt;&lt;span&gt;\U&lt;/span&gt;&lt;span&gt;sers&lt;/span&gt;&lt;span&gt;\a&lt;/span&gt;&lt;span&gt;mara&lt;/span&gt;&lt;span&gt;\A&lt;/span&gt;&lt;span&gt;ppData&lt;/span&gt;&lt;span&gt;\w&lt;/span&gt;&lt;span&gt;sl&lt;/span&gt;&lt;span&gt;\u&lt;/span&gt;&lt;span&gt;buntu2204.tar&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ubuntu2404&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;E:&lt;/span&gt;&lt;span&gt;\U&lt;/span&gt;&lt;span&gt;sers&lt;/span&gt;&lt;span&gt;\&amp;lt;&lt;/span&gt;&lt;span&gt;your_windows_username&amp;gt;&lt;/span&gt;&lt;span&gt;\A&lt;/span&gt;&lt;span&gt;ppData&lt;/span&gt;&lt;span&gt;\w&lt;/span&gt;&lt;span&gt;sl&lt;/span&gt;&lt;span&gt;\u&lt;/span&gt;&lt;span&gt;buntu2404&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;E:&lt;/span&gt;&lt;span&gt;\U&lt;/span&gt;&lt;span&gt;sers&lt;/span&gt;&lt;span&gt;\a&lt;/span&gt;&lt;span&gt;mara&lt;/span&gt;&lt;span&gt;\A&lt;/span&gt;&lt;span&gt;ppData&lt;/span&gt;&lt;span&gt;\w&lt;/span&gt;&lt;span&gt;sl&lt;/span&gt;&lt;span&gt;\u&lt;/span&gt;&lt;span&gt;buntu2404.tar&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;利用 &lt;code&gt;wsl --import&lt;/code&gt;，你可以将同一个 &lt;code&gt;.tar&lt;/code&gt; 包导入多次并赋予不同名称，从而实现多开同一个发行版。虽然在隔离环境方面类似于 Docker，但 WSL2 原生支持 WSLg，可以直接运行 Linux 图形化应用，这在某些桌面开发场景下比 Docker 更具优势。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.3 恢复默认用户&lt;a href=&quot;#33-恢复默认用户&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;导入完成并启动新的分发后，你可能会发现默认登录用户变成了 &lt;code&gt;root&lt;/code&gt;。我们需要将其改回你原本的 Linux 用户名。如果你的分发并没有发现此类问题，可以跳过本节。&lt;/p&gt;&lt;p&gt;进入新的 WSL 实例：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ubuntu2204&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;使用 &lt;code&gt;nano&lt;/code&gt; 编辑器修改 WSL 配置文件（如果你更习惯图形化界面，也可以执行 &lt;code&gt;apt update &amp;amp;&amp;amp; apt install gedit -y&lt;/code&gt; 后使用 &lt;code&gt;gedit&lt;/code&gt;）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nano&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/etc/wsl.conf&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;在文件中添加或修改以下内容（将 &lt;code&gt;&amp;lt;your_linux_username&amp;gt;&lt;/code&gt; 替换为你在 WSL 中的用户名）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;[user]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;lt;your_linux_username&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;保存并退出：依次按下 &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;X&lt;/kbd&gt;，输入 &lt;kbd&gt;Y&lt;/kbd&gt; 确认保存，最后按 &lt;kbd&gt;Enter&lt;/kbd&gt; 退出。&lt;/p&gt;&lt;p&gt;退出当前 WSL 会话，并在 Windows 终端中重启改实例使配置生效：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 终止实例&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--terminate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ubuntu2204&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 重新进入验证用户是否恢复正常&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ubuntu2204&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.4 设置为默认分发并清理善后&lt;a href=&quot;#34-设置为默认分发并清理善后&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;如果一切验证无误，将新的实例设置为系统的默认 WSL 分发：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--set-default&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ubuntu2404&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;确认无误后，就可以卸载 C 盘中的旧分发了：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--unregister&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Ubuntu-22.04&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wsl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--unregister&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Ubuntu-24.04&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;最后，删除存放在 E 盘的过渡文件 &lt;code&gt;.tar&lt;/code&gt; 压缩包以释放空间。此时再次执行 &lt;code&gt;wsl -l -v&lt;/code&gt;，你应该能看到干净清爽的列表了。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;4 总结&lt;a href=&quot;#4-总结&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;迁移安装最大的好处就是能显著减小 C 盘的存储压力。不过，如果你的物理磁盘总容量本身较小（比如只有 512GB 单盘），笔者依然更推荐直接重装系统并只保留一个 C 盘分区，统一管理存储空间。&lt;/p&gt;&lt;p&gt;此外，根据&lt;a href=&quot;https://bfmhno3.github.io/tutorial/wsl2-image-migration-guide/&quot;&gt;微软官方文档的建议&lt;/a&gt;，为了获得最佳的文件 I/O 性能，进行 Linux 相关的开发时，请务必将项目文件直接存储在 WSL 的 Linux 文件系统（如 &lt;code&gt;~&lt;/code&gt; 目录下），而不是跨文件系统存储在 Windows 盘符（如 &lt;code&gt;/mnt/c&lt;/code&gt;）中。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://superuser.com/questions/1701175/installing-ubuntu-on-mnt-d-with-wsl&quot; target=&quot;_blank&quot;&gt;Installing Ubuntu on /mnt/d with WSL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/OsbertWang/install-latex-guide-zh-cn&quot; target=&quot;_blank&quot;&gt;install-latex-guide-zh-cn (附录B.5 迁移 WSL 的安装位置)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;</content:encoded></item><item><title>C++ 中的 stack</title><link>https://bfmhno3.github.io/posts/stack-in-cpp/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/stack-in-cpp/</guid><description>本文从本质需求出发解释栈（LIFO）为何存在，串联函数调用、递归、撤销与括号匹配等典型场景，并结合 C++ STL 的 std::stack 用法、异常安全设计与底层容器选择，最后配合多道题目示例完成从概念到实战的闭环。</description><pubDate>Thu, 19 Feb 2026 02:45:00 GMT</pubDate><content:encoded>&lt;p&gt;要真正理解&lt;strong&gt;栈&lt;/strong&gt;这种结构，意味着我们要剥离事物表象，追究其最本质的问题，即&lt;strong&gt;我们到底面临什么样的数据处理需求，才导致了 “栈” 这种结构的诞生？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们不需要把栈仅仅看作是教科书上的定义，而是把它看作是&lt;strong&gt;为了解决特定时序问题而设计的一种 “受限” 模型&lt;/strong&gt;。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;1. 什么是栈？&lt;a href=&quot;#1-什么是栈&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;从本质上来说，计算机处理任务通常有两种模式：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;并行/随机&lt;/strong&gt;：所有任务都在桌面上铺开，想拿哪个拿哪个（类似数组或哈希表）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;嵌套/依赖&lt;/strong&gt;：任务 B 必须依赖任务 A 的环境，任务 C 又依赖 B。要完成 A，必须先挂起 A 去做 B；要做 B，必须先挂起 B 去做 C。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;栈&lt;/strong&gt;（Stack）就是为了解决第二种模式而诞生的。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;1.1 核心定义：LIFO&lt;a href=&quot;#11-核心定义lifo&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;栈是一种&lt;strong&gt;线性表&lt;/strong&gt;，但它是一种受限的线性表。它的核心逻辑是&lt;strong&gt;后进先出&lt;/strong&gt;（Last In, First Out - LIFO）。&lt;/p&gt;&lt;p&gt;你可以把它想象成&lt;strong&gt;叠盘子&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;压入（Push）：你只能把新盘子放在最上面。&lt;/li&gt;
&lt;li&gt;弹出（Pop）：你只能取走最上面的盘子。&lt;/li&gt;
&lt;li&gt;查看（Top/Peek）：你只能看到最上面盘子的花纹，无法直接看到底部的盘子。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/a_stack_of_plates.jpg&quot; alt=&quot;生活中的栈模型：想要拿到底部的盘子，必须先移走顶部的盘子&quot; /&gt;&lt;figcaption&gt;生活中的栈模型：想要拿到底部的盘子，必须先移走顶部的盘子&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;1.2 为什么它是 “受限” 的？&lt;a href=&quot;#12-为什么它是-受限-的&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;数组（Array）允许你访问 &lt;code&gt;index[0]&lt;/code&gt; 也可以访问 &lt;code&gt;index[100]&lt;/code&gt;。栈为什么要把这种能力 “阉割” 掉？&lt;/p&gt;&lt;p&gt;答案就是&lt;strong&gt;为了强制秩序&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;通过限制 “只能操作顶部”，栈强制程序员必须按照严格的 “原路返回” 逻辑来处理数据，从而消除了随机访问带来的复杂性和潜在错误。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2. 为什么需要栈？&lt;a href=&quot;#2-为什么需要栈&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果计算机世界没有栈，很多基础逻辑就会崩塌。我们需要栈，是因为它是&lt;strong&gt;保存上下文&lt;/strong&gt;（Context）和&lt;strong&gt;实现回朔&lt;/strong&gt;（Backtacking）最自然的模型。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;2.1 函数调用（The Call Stack）&lt;a href=&quot;#21-函数调用the-call-stack&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是栈存在最底层、也最不可或缺的原因。&lt;/p&gt;&lt;p&gt;从本质上来说，CPU 在执行代码时是个高度专注的执行机器，它完全依赖程序计数器（PC, Program Counter）来决定下一步跑哪条指令。当代码从 &lt;code&gt;main()&lt;/code&gt; 跳转到 &lt;code&gt;func_a()&lt;/code&gt; 时，本质上是强行修改了 PC 寄存器的值。随之而来的致命问题是：当 &lt;code&gt;func_a()&lt;/code&gt; 跑完，CPU 怎么知道该跳回 &lt;code&gt;main()&lt;/code&gt; 里的哪一行继续执行？&lt;/p&gt;&lt;p&gt;你可能会想，用一个专门的寄存器（比如链接寄存器 LR）把返回地址保存起来不就行了？&lt;/p&gt;&lt;p&gt;在只有单层调用的情况下，这确实可行。但现实是，代码中充满了&lt;strong&gt;嵌套调用&lt;/strong&gt;：&lt;code&gt;main()&lt;/code&gt; 调用了 &lt;code&gt;func_a()&lt;/code&gt;，而 &lt;code&gt;func_a()&lt;/code&gt; 中又调用了 &lt;code&gt;func_b()&lt;/code&gt;。如果只依赖单一寄存器，当调起 &lt;code&gt;func_b()&lt;/code&gt; 时，新的返回地址回无情地覆盖掉之前 &lt;code&gt;func_a()&lt;/code&gt; 的返回地址。等到 &lt;code&gt;func_b()&lt;/code&gt; 执行结束，程序就再也找不到回 &lt;code&gt;main()&lt;/code&gt; 的路了。&lt;/p&gt;&lt;p&gt;此外，函数不仅需要记住 “从哪儿来”，还要保护好自己的 “现场”。每个函数都有自己的局部变量，执行时还会征用各种通用寄存器。在跳入下一个函数前，当前的这些状态（Context）必须被原封不动地保存下来。&lt;/p&gt;&lt;p&gt;由于函数调用的逻辑是绝对的 “嵌套与回溯”——最后被调用的 &lt;code&gt;func_b()&lt;/code&gt; 必须最先执行完毕并交出控制权，&lt;code&gt;func_a()&lt;/code&gt; 才能继续，做后才轮到最外层的 &lt;code&gt;main()&lt;/code&gt;。这种 “&lt;strong&gt;后出发的先结束，先出发的最后兜底&lt;/strong&gt;” 的生命周期，与 LIFO (Last-In, First-Out) 结构严丝合缝。&lt;/p&gt;&lt;p&gt;所有，系统在内存中专门开辟了&lt;strong&gt;调用栈&lt;/strong&gt;（Call Stack）。每次进入新函数，就把它的返回地址、局部变量和寄存器状态打包成一个&lt;strong&gt;栈帧&lt;/strong&gt;（Stack Frame）压入栈顶；函数一结束，就将栈顶&lt;strong&gt;弹出&lt;/strong&gt;，完美恢复上一层函数的现场。没有栈，现代编程语言的函数嵌套和递归机制就根本无法实现。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/call_stack_layout.svg&quot; alt=&quot;调用栈示例：如果子程序 DrawSquare 调用了 DrawLine，DrawLine 的栈帧就会被压入栈顶（如图中绿色部分所示）。栈指针（Stack Pointer）始终指向最新的栈顶位置&quot; /&gt;&lt;figcaption&gt;调用栈示例：如果子程序 DrawSquare 调用了 DrawLine，DrawLine 的栈帧就会被压入栈顶（如图中绿色部分所示）。栈指针（Stack Pointer）始终指向最新的栈顶位置&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.2 递归（Recursion）&lt;a href=&quot;#22-递归recursion&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;既然函数调用依赖栈来保存上下文，那么&lt;strong&gt;递归&lt;/strong&gt;可以说是把栈的这种特性压榨到了机制。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/recursive_drawing_of_a_sierpinski_triangle_through_turtle_graphics.gif&quot; alt=&quot;使用海龟绘图（Turtle Graphics）递归绘制谢尔宾斯基三角形（Sierpinski triangle）的过程&quot; /&gt;&lt;figcaption&gt;使用海龟绘图（Turtle Graphics）递归绘制谢尔宾斯基三角形（Sierpinski triangle）的过程&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;从人类的思维来看，递归是&lt;strong&gt;自己调用自己&lt;/strong&gt;，但在 CPU 和内存眼里，根本没有 “同一个函数” 的概念。对于计算机而言，不管是 &lt;code&gt;func_a()&lt;/code&gt; 调用 &lt;code&gt;func)b()&lt;/code&gt;，还是 &lt;code&gt;func_a()&lt;/code&gt; 调用 &lt;code&gt;func_a()&lt;/code&gt;，动作是完全一样的——仅仅是 PC（程序计数器）指针跳转，并在内存中开辟一个新的栈帧。&lt;/p&gt;&lt;p&gt;假设我们在写一个计算阶乘的递归函数（&lt;code&gt;fact(n)&lt;/code&gt;）。当 &lt;code&gt;func(3)&lt;/code&gt; 内部调用 &lt;code&gt;fact(2)&lt;/code&gt; 时，此时的局部变量 &lt;code&gt;n=3&lt;/code&gt; 必须被冻结并保护起来。如果没有栈 &lt;code&gt;fact(2)&lt;/code&gt; 内部的变量就会直接覆盖掉原来 &lt;code&gt;n=3&lt;/code&gt; 的内存空间。等到递归触底反弹时，程序根本想不起最初传入的参数是什么。&lt;/p&gt;&lt;p&gt;得益于栈的 LIFO 机制，每一次自我调用，系统都会把当前的参数、局部变量和返回地址像叠盘子一样&lt;strong&gt;压入&lt;/strong&gt;（Push）栈中。不管向下深挖了多少层，每一层的数据都被独立且安全地封存在自己的栈帧里。当遇到终止条件（Base Case）开始回归时，栈再一层层&lt;strong&gt;弹出&lt;/strong&gt;（Pop），精准地恢复每一层的现场。可以说，&lt;strong&gt;栈时递归能够成立的物理载体&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;当然，如果嵌套层数过深导致栈空间耗尽，就会引发经典的 Stack Overflow 栈溢出。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.3 状态撤销（Undo）与历史回溯&lt;a href=&quot;#23-状态撤销undo与历史回溯&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;你在编辑器里敲击键盘，后者在设计软件中画图，想要按下 &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Z&lt;/kbd&gt; 撤销刚刚的失误。&lt;/p&gt;&lt;p&gt;人类的时间是线性向前的，但 “撤销” 操作要求我们能够&lt;strong&gt;逆转时间&lt;/strong&gt;。这种逆转不是随机的，而是必须严格遵守 “最近发生的事情，最先被抹去” 的自然法则。你最后敲下的那一个字符，必须是第一个被删除的。&lt;/p&gt;&lt;p&gt;所以，这里的历史记录被抽象成了一个栈：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;入栈&lt;/strong&gt;（Push）：每当你执行一个操作（比如输入字符、改变颜色、移动光标），系统就会把这个动作的 “状态快照” 或 “增量数据” 打包，压入历史记录栈的栈顶。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;出栈&lt;/strong&gt;（Pop）：当年你触发撤销（Undo）时，系统直接从栈顶取回最后一次保存的状态，并将其弹出，界面随之恢复到上一个时刻。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;没有栈这种 “后进先出” 的模型，系统就无法建立起具有时间顺序的后悔药机制。&lt;strong&gt;任何需要 “原路” 返回的场景&lt;/strong&gt;（比如浏览器的 “后退” 按钮、迷宫算法的回溯路径），其背后的灵魂数据机构都是栈。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.4 符号匹配与语法解析&lt;a href=&quot;#24-符号匹配与语法解析&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;编译器在编译代码时，或者解析器在处理一段复杂公式时，需要检查代码中的 &lt;code&gt;{ [ ( ) ] }&lt;/code&gt; 括号是否成对闭合、合法嵌套。&lt;/p&gt;&lt;p&gt;编译器阅读代码的顺序是从左到右的单向扫描。但在代码的世界里，&lt;strong&gt;最晚开启的作用于，必须最早被关闭&lt;/strong&gt;。这种一种典型的 LIFO 结构需求。&lt;/p&gt;&lt;p&gt;面对一长串括号，系统处理的算法极其优雅，完全依赖栈来实现：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;扫描与入栈&lt;/strong&gt;：逐个字符读取，只要遇到 “左括号”（如 &lt;code&gt;{&lt;/code&gt;、&lt;code&gt;[&lt;/code&gt;、&lt;code&gt;(&lt;/code&gt;），不管三七二十一，直接压入栈中。这代表着进入了一个新的层级，正在等待它的结束。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;匹配与出栈&lt;/strong&gt;：一旦遇到 “右括号”（如 &lt;code&gt;)&lt;/code&gt;、&lt;code&gt;]&lt;/code&gt;、&lt;code&gt;}&lt;/code&gt;），此时立刻去查看&lt;strong&gt;栈顶&lt;/strong&gt;元素。如果栈顶正好是能与它匹配的左括号，说明当前最内层的逻辑闭环了，于是将栈顶弹出，继续往下走。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/parentheses_matching.webp&quot; alt=&quot;利用栈进行括号匹配的逻辑：左括号入栈，右括号消栈，最后栈空则合法&quot; /&gt;&lt;figcaption&gt;利用栈进行括号匹配的逻辑：左括号入栈，右括号消栈，最后栈空则合法&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;如果遇到右括号时栈是空的（说明没有多余的左括号等待闭合），或者栈顶的左括号类型不匹配（比如 &lt;code&gt;{&lt;/code&gt; 遇到了 &lt;code&gt;]&lt;/code&gt;），那么编译器就可以立刻抛出 &lt;code&gt;Syntax Error&lt;/code&gt;（语法错误）。当整个文件扫描完，如果栈里面还有残留的左括号，说明有作用域没关上。&lt;/p&gt;&lt;p&gt;利用栈，编译器不仅能判断对错，还能精准定位到哪一层的括号出了问题。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;3. C++ STL 中的 &lt;code&gt;std::stack&lt;/code&gt;&lt;a href=&quot;#3-c-stl-中的-stdstack&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在 C++ 标准模板库（STL）中，&lt;code&gt;std::stack&lt;/code&gt; 被设计得非常精妙。它不是一个从零开始写的容器，而是一个&lt;strong&gt;容器适配器&lt;/strong&gt;（Container Adapter）。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;3.1 什么是 “容器适配器”？&lt;a href=&quot;#31-什么是-容器适配器&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;std::stack&lt;/code&gt; 本身不直接管理内存，它更像是一个外壳。它内部包裹着另一个真正的容器（比如 &lt;code&gt;std::vector&lt;/code&gt;、&lt;code&gt;std::deque&lt;/code&gt; 后 &lt;code&gt;std::list&lt;/code&gt;）。&lt;/p&gt;&lt;p&gt;它屏蔽了底层容器的随机访问结构（如 &lt;code&gt;[]&lt;/code&gt;），只暴露 &lt;code&gt;push&lt;/code&gt;、&lt;code&gt;pop&lt;/code&gt;、&lt;code&gt;top&lt;/code&gt; 等接口，从而强制你遵守栈的规则。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.2 核心语法与操作&lt;a href=&quot;#32-核心语法与操作&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在使用前需包含头文件：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;stack&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;基本操作（时间复杂度均为 &lt;span&gt;&lt;span&gt;O(1)O(1)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;）。&lt;/p&gt;














































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;操作&lt;/th&gt;&lt;th&gt;函数&lt;/th&gt;&lt;th&gt;描述&lt;/th&gt;&lt;th&gt;注意事项&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;入栈&lt;/td&gt;&lt;td&gt;&lt;code&gt;push(val)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;将元素拷贝到栈顶&lt;/td&gt;&lt;td&gt;设计拷贝/移动构造&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;原地构造&lt;/td&gt;&lt;td&gt;&lt;code&gt;emplace(args)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;在栈顶直接构造元素&lt;/td&gt;&lt;td&gt;&lt;strong&gt;推荐&lt;/strong&gt;，比 &lt;code&gt;push&lt;/code&gt; 更高效，省去拷贝&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;出栈&lt;/td&gt;&lt;td&gt;&lt;code&gt;pop()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;移除栈顶元素&lt;/td&gt;&lt;td&gt;&lt;strong&gt;返回值为 &lt;code&gt;void&lt;/code&gt;&lt;/strong&gt;，不返回被移除的元素&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;查看&lt;/td&gt;&lt;td&gt;&lt;code&gt;top()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;返回栈顶元素的引用&lt;/td&gt;&lt;td&gt;如果栈为空调用此函数回导致未定义行为（通常崩溃）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;判空&lt;/td&gt;&lt;td&gt;&lt;code&gt;empty()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;栈为空返回 &lt;code&gt;true&lt;/code&gt;&lt;/td&gt;&lt;td&gt;操作前务必检查&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;大小&lt;/td&gt;&lt;td&gt;&lt;code&gt;size()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;返回元素数量&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/a_stack_runtime_with_push_and_pop_operations.svg&quot; alt=&quot;Push 操作将元素压入栈顶，Pop 操作移除栈顶元素，两者都不能触及栈底&quot; /&gt;&lt;figcaption&gt;Push 操作将元素压入栈顶，Pop 操作移除栈顶元素，两者都不能触及栈底&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3.3 为什么 C++ 的 &lt;code&gt;pop()&lt;/code&gt; 不返回元素？&lt;a href=&quot;#33-为什么-c-的-pop-不返回元素&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在很多其他语言（如 Java, Python）中，&lt;code&gt;pop()&lt;/code&gt; 会移除并返回栈顶元素。但在 C++ STL 中，&lt;code&gt;pop()&lt;/code&gt; 是 &lt;code&gt;void&lt;/code&gt;，你需要先 &lt;code&gt;top()&lt;/code&gt; 获取值，再 &lt;code&gt;pop()&lt;/code&gt; 移除。&lt;/p&gt;&lt;p&gt;究其原因是&lt;strong&gt;异常安全&lt;/strong&gt;（Exception Safety）。&lt;/p&gt;&lt;p&gt;如果 &lt;code&gt;pop()&lt;/code&gt; 函数即移除元素又返回元素（按值返回），在返回过程中如果发生了&lt;strong&gt;拷贝构造异常&lt;/strong&gt;（比如内存不足），那么这个元素即从栈里丢了，又没能成功交给调用者，导致数据永久丢失。&lt;/p&gt;&lt;p&gt;将 &lt;code&gt;top()&lt;/code&gt; 和 &lt;code&gt;pop()&lt;/code&gt; 分离是 C++ 追求安全的体现。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;4. 算法题实战&lt;a href=&quot;#4-算法题实战&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;点击题目即可跳转。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;4.1 &lt;a href=&quot;https://www.luogu.com.cn/problem/B3614&quot; target=&quot;_blank&quot;&gt;洛谷：B3614【模板】栈&lt;/a&gt;&lt;a href=&quot;#41-洛谷b3614模板栈&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;cstdint&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;string&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;namespace&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;uint64_t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;uint64_t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;uint64_t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;top_idx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; // 栈顶指针，指向栈顶元素的下一个位置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;uint64_t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;cin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; // 数据组数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;top_idx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; // 每组数据都清空栈&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;uint64_t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; // 操作的次数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;cin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;operation&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;uint64_t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;cin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;operation&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&quot;push&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;operation&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;cin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;top_idx&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&quot;pop&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;operation&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;top_idx&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Empty&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;endl&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt;top_idx&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&quot;query&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;operation&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;top_idx&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Anguei!&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;endl&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;top_idx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;endl&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&quot;size&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;operation&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;top_idx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;endl&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4.2 &lt;a href=&quot;https://leetcode.cn/problems/valid-parentheses/description/&quot; target=&quot;_blank&quot;&gt;力扣：20. 有效的括号&lt;/a&gt;&lt;a href=&quot;#42-力扣20-有效的括号&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Solution&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;isValid&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;stack&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&apos;(&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;[&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;{&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;empty&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&apos;)&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;(&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&apos;]&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;[&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&apos;}&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;{&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;empty&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4.3 &lt;a href=&quot;https://www.luogu.com.cn/problem/P4387&quot; target=&quot;_blank&quot;&gt;洛谷：【深基15.习9】验证栈序列&lt;/a&gt;&lt;a href=&quot;#43-洛谷深基15习9验证栈序列&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;vector&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;stack&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;constexpr&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1000000&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;pushed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;popped&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;q&lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 读取入栈序列&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pushed&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 读取出栈序列&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;popped&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 模拟验证&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;stack&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;popped_idx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 依次入栈&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pushed&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;popped_idx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;empty&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;popped&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;popped_idx&lt;/span&gt;&lt;span&gt;]) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;popped_idx&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 判断&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;empty&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Yes&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;endl&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;No&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;endl&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;50&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;51&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;52&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4.4 &lt;a href=&quot;https://www.luogu.com.cn/problem/P1449&quot; target=&quot;_blank&quot;&gt;洛谷：P1449 后缀表达式&lt;/a&gt;&lt;a href=&quot;#44-洛谷p1449-后缀表达式&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;stack&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;string&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;stack&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;num_str&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;num&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;@&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span&gt;isdigit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;static_cast&lt;/span&gt;&lt;span&gt;&amp;lt;unsigned&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&amp;gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt;))) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;num_str&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;.&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;num_str&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;empty&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span&gt;stoi&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;num_str&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;num_str&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;clear&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;switch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;+&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;-&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;*&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;case&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;/&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;endl&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4.5 &lt;a href=&quot;https://www.luogu.com.cn/problem/P1241&quot; target=&quot;_blank&quot;&gt;洛谷：P1241 括号序列&lt;/a&gt;&lt;a href=&quot;#45-洛谷p1241-括号序列&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;string&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;stack&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;vector&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is_match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&apos;(&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;)&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&apos;[&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;]&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;stack&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;matched&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;static_cast&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;()); &lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&apos;(&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;[&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;empty&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is_match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt;()], &lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;matched&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt;()] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;matched&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;static_cast&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;()); &lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;matched&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;]) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&apos;(&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;)&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;expression&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;]) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;()&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;[]&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;endl&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;5. 进阶知识（底层容器的选择）&lt;a href=&quot;#5-进阶知识底层容器的选择&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://en.cppreference.com/w/cpp/container/stack.html&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;std::stack&lt;/code&gt;&lt;/a&gt; 是一个模板类（定义在头文件 &lt;code&gt;&amp;lt;stack&amp;gt;&lt;/code&gt;），定义如下：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Container&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;deque&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stack&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;注意第二个参数 &lt;code&gt;Container&lt;/code&gt;。默认情况下，C++ 使用 &lt;code&gt;std::deque&lt;/code&gt;（双端队列）作为底层容器，而不是 &lt;code&gt;std::vector&lt;/code&gt;。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;5.1 为什么默认用 &lt;code&gt;std::deque&lt;/code&gt; 而不是 &lt;code&gt;std::vector&lt;/code&gt;？&lt;a href=&quot;#51-为什么默认用-stddeque-而不是-stdvector&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;扩容代价&lt;/strong&gt;：&lt;code&gt;std::vector&lt;/code&gt; 在内存不足扩容时，需要把所有元素搬运到新内存块，代价较大。&lt;code&gt;std::deque&lt;/code&gt; 是分段内存，扩容时只需分配新段，不需要搬运旧数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内存效率&lt;/strong&gt;：对于大量进出的栈操作，&lt;code&gt;std::deque&lt;/code&gt; 的内存回收和分配策略通常比 &lt;code&gt;std::vector&lt;/code&gt; 更平滑。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/double_ended_queue_in_stl.png&quot; alt=&quot;std::deque 的内部结构：由中控器（Map）管理的一系列分段连续内存缓冲区，非常适合两端操作&quot; /&gt;&lt;figcaption&gt;std::deque 的内部结构：由中控器（Map）管理的一系列分段连续内存缓冲区，非常适合两端操作&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;当然，如果你确定需要极致的连续内存性能，也可以手动指定：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;stack&lt;/span&gt;&lt;span&gt;&amp;lt;int&lt;/span&gt;&lt;span&gt;, std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stk&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Stack_(abstract_data_type)&quot; target=&quot;_blank&quot;&gt;Wikipedia: Stack (abstract data type)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Call_stack&quot; target=&quot;_blank&quot;&gt;Wikipedia: Call stack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Recursion_(computer_science)&quot; target=&quot;_blank&quot;&gt;Wikipedia: Recursion (computer science)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@goyalarchana17/scala-interview-series-valid-parentheses-in-a-string-ac7002c79642&quot; target=&quot;_blank&quot;&gt;Medium: Valid Parentheses in a String&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/1436020/whats-the-difference-between-deque-and-list-stl-containers&quot; target=&quot;_blank&quot;&gt;StackOverflow: Difference between deque and list&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cppreference.com/w/cpp/container/stack&quot; target=&quot;_blank&quot;&gt;cppreference.com: std::stack&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;</content:encoded></item><item><title>操作系统中的进程</title><link>https://bfmhno3.github.io/posts/process-in-os/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/process-in-os/</guid><description>教科书对于进程常定义通常极为简洁凝练，但其诞生是为了解决什么问题？本文通过回顾从 ENIAC 到分时系统的演进历史，探讨进程是如何为了填补人、I/O 与 CPU 之间的速度鸿沟而进化出来的。</description><pubDate>Mon, 26 Jan 2026 22:10:00 GMT</pubDate><content:encoded>&lt;p&gt;在计算机科学的教科书中，关于&lt;strong&gt;进程&lt;/strong&gt;（&lt;em&gt;Process&lt;/em&gt;）的定义往往只有冷冰冰的一句话：&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;进程是程序的一次执行过程。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;这句话虽然准确，但它掩盖了太多细节。对于初学者而言，这更像是一个黑盒。为什么要有进程？为什么不能直接操作物理地址？所谓的&lt;strong&gt;上下文切换&lt;/strong&gt;到底切换了什么？&lt;/p&gt;
&lt;p&gt;要真正理解进程，我们不能只看定义，而必须回到计算机发展的历史，从本质出发，看看是怎样的资源瓶颈和物理限制，逼迫工程师们发明了&lt;strong&gt;进程&lt;/strong&gt;这个概念。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;0. 起点：硬件即程序&lt;a href=&quot;#0-起点硬件即程序&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;












&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;时间背景&lt;/th&gt;&lt;th&gt;代表&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1940s - 1950s 初&lt;/td&gt;&lt;td&gt;ENIAC&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;在&lt;strong&gt;进程&lt;/strong&gt;这个概念诞生之前，计算机的世界是纯粹且静止的。如果非要为这个 “史前时代” 选一个代表，那非 &lt;a href=&quot;https://en.wikipedia.org/wiki/ENIAC&quot; target=&quot;_blank&quot;&gt;ENIAC&lt;/a&gt;（Electroinc Numeric Numerical Integrator and Computer，电子数值计算机）莫属。在这个阶段，软件和硬件之间并没有明显界限，甚至可以说，根本没有现代意义上的“软件”。&lt;/p&gt;&lt;p&gt;想象一下，你面对的是一个占地 167 平方米、重达 27 吨的庞然大物。这就是 ENIAC，它是那个时代算力的巅峰，但对于 “任务管理” 而言，它确实最原始的形态：它没有任何中间层来管理资源；同一时间，这台巨兽只能为解决&lt;strong&gt;一个问题&lt;/strong&gt;而存在。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/Glen_Beck_and_Betty_Snyder_program_the_ENIAC_in_building_328_at_the_Ballistic_Research_Laboratory.jpg&quot; alt=&quot;早期的编程：Glen Beck 与 Betty Snyder 正在对 ENIAC 进行硬连线&quot; /&gt;&lt;figcaption&gt;早期的编程：Glen Beck 与 Betty Snyder 正在对 ENIAC 进行硬连线&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;在 ENIAC 上 “切换任务” 并不是像今天一样双击一个图标一样简单。那是一场体力劳动。当时的 “编程”，实际上就是 “接线”。&lt;/p&gt;&lt;p&gt;程序员（通常是女性数学家）需要拿着粗大的连接线（Patch Cables），在巨大的配线板上进行物理连接。每一个插孔的连接，每一排开关的拨动，都代表着指令的逻辑流向。当你把线插好，这台机器就变成了解决那个特定方程的专用电路。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/Two_women_operating_ENIAC.jpg&quot; alt=&quot;操作员正在手动更改 ENIAC 的线路配置&quot; /&gt;&lt;figcaption&gt;操作员正在手动更改 ENIAC 的线路配置&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;似乎有点类似于现在的 FPGA。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;既然没有操作系统，那谁来切换任务呢？那自然也只能让人来手动切换了。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/Classic_shot_of_the_ENIAC.jpg&quot; alt=&quot;ENIAC 全景：巨大的体积与原始的操作方式&quot; /&gt;&lt;figcaption&gt;ENIAC 全景：巨大的体积与原始的操作方式&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;在这个时期，ENIAC 的加法速度已经高达 5000 次/秒，但准备计算的过程确慢得令人发指。而正是这种&lt;strong&gt;极高的计算速度&lt;/strong&gt;与&lt;strong&gt;极低的任务切换效率&lt;/strong&gt;之间的巨大鸿沟，成为了推动计算机科学向前发展的原动力。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;1. 第一次进化：消除人的延迟 —— 批处理系统&lt;a href=&quot;#1-第一次进化消除人的延迟--批处理系统&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;












&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;时间背景&lt;/th&gt;&lt;th&gt;代表&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1955 - 1960s 初&lt;/td&gt;&lt;td&gt;GM-NAA I/O（为 IBM 704 开发）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;1945 年，随着 &lt;a href=&quot;https://en.wikipedia.org/wiki/John_von_Neumann&quot; target=&quot;_blank&quot;&gt;John von Neumann&lt;/a&gt; 在其里程碑式的著作《&lt;a href=&quot;https://en.wikipedia.org/wiki/First_Draft_of_a_Report_on_the_EDVAC&quot; target=&quot;_blank&quot;&gt;First Draft of a Report on the EDVAC&lt;/a&gt;》中正式确立了冯·诺依曼架构，程序终于从繁琐的硬件连线进化为存储在磁带上的指令。虽然这一边个消除了物理插拔线缆的需求，但在早期的 Open Shop（开放式机房）模式依然由人主导：程序员带着磁带走进机房，装带、运行、卸带、离开，然后下一个人进来。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/Von_Neumann_Architecture.svg&quot; alt=&quot;冯·诺依曼架构：存储程序计算机的基石&quot; /&gt;&lt;figcaption&gt;冯·诺依曼架构：存储程序计算机的基石&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;当时的 CPU（如 IBM 704）如同黄金般珍贵。人的动作（装磁带、按按钮）可能需要花费几分钟，但 CPU 可能几秒钟就执行完毕。&lt;/p&gt;&lt;p&gt;让一台每秒运行数万次指令的 CPU 停下来等人，这是对计算资源极大的浪费。为了解决 “人太慢，CPU 等人” 的矛盾，&lt;strong&gt;批处理系统&lt;/strong&gt;（&lt;em&gt;Batch Processing&lt;/em&gt;）应运而生。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/batch_processing_system.png&quot; alt=&quot;批处理系统的工作流示意图&quot; /&gt;&lt;figcaption&gt;批处理系统的工作流示意图&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;1956 年，通用汽车（GM）的研究部门为 IBM 704 编写了 &lt;a href=&quot;https://en.wikipedia.org/wiki/GM-NAA_I/O&quot; target=&quot;_blank&quot;&gt;GM-NAA I/O&lt;/a&gt;，这被认为是世界上第一个真正意义上的操作系统。&lt;/p&gt;&lt;p&gt;工程师们发明了一个叫做 &lt;a href=&quot;https://en.wikipedia.org/wiki/Resident_monitor&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;Monitor&lt;/strong&gt;&lt;/a&gt; 监控程序的常驻小软件。它的逻辑非常简单：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;它始终停留在内存的一小块区域。&lt;/li&gt;
&lt;li&gt;它负责读取磁盘上的下一个&lt;strong&gt;作业&lt;/strong&gt;（Job）。&lt;/li&gt;
&lt;li&gt;自动加载作业 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 运行作业 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 作业结束 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 把控制权还给 Monitor &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 加载下一个。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/resident_montior.png&quot; alt=&quot;常驻监控程序 (Monitor) 的内存布局&quot; /&gt;&lt;figcaption&gt;常驻监控程序 (Monitor) 的内存布局&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;此时，依然没有&lt;strong&gt;进程&lt;/strong&gt;的概念，只有&lt;strong&gt;作业&lt;/strong&gt;（Job）。内存里依然一次只住一个程序。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2. 第二次进化：消除 I/O 的延迟 —— 多道程序设计&lt;a href=&quot;#2-第二次进化消除-io-的延迟--多道程序设计&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;












&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;时间背景&lt;/th&gt;&lt;th&gt;代表&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1960s 中期&lt;/td&gt;&lt;td&gt;IBM OS/360&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;批处理虽然解决了人的问题，但没解决硬件的问题。即使是自动运行，当程序需要读取磁带或磁盘时，由于机械设备的物理限制，I/O 操作的速度比 CPU 慢了几个数量级（毫秒 vs 微秒）。&lt;/p&gt;&lt;p&gt;每当程序发起 I/O 请求（比如读取数据块），CPU 就不得不停下来傻等。这种等待在 CPU 眼里是极其漫长的。&lt;/p&gt;&lt;p&gt;为了解决 “I/O 太慢，CPU 等 I/O” 的矛盾，&lt;strong&gt;多道程序设计&lt;/strong&gt;（Multiprogramming）横空出世。IBM 在其传奇的 Systen/360 大型机上大力推行这一概念。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/IBM_System_360_model_30_cpu.jpg&quot; alt=&quot;IBM System/360 Model 30 控制台&quot; /&gt;&lt;figcaption&gt;IBM System/360 Model 30 控制台&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;工程师们想出了一个绝妙的主意：既然内存变大了，为什么不一次性装入多个作业呢？&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;当 Job A 在等待磁带数据时（阻塞），操作系统把 CPU 切给 Job B 用。&lt;/li&gt;
&lt;li&gt;当 Job B 也在等打印机时，再切给 Job C。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/multiprogramming_operating_system.png&quot; alt=&quot;多道程序设计下的内存划分&quot; /&gt;&lt;figcaption&gt;多道程序设计下的内存划分&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;这一刻，计算机科学迎来了一个关键的转折点：为了在多个程序之间来回切换，我们需要保存每个程序的状态。&lt;/p&gt;&lt;p&gt;在 OS/360 中，出现了用来描述任务状态的数据结构 &lt;strong&gt;TCB&lt;/strong&gt;（Task Control Block）。这正是后来 &lt;strong&gt;PCB&lt;/strong&gt;（Process Control Block）的前身。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/os_360_system_control_blocks.jpg&quot; alt=&quot;OS/360 中的系统控制块 (TCB) 结构&quot; /&gt;&lt;figcaption&gt;OS/360 中的系统控制块 (TCB) 结构&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;此时，TCB 主要完成两个任务：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;现场保护&lt;/strong&gt;：当 CPU 从 A 切到 B 时，A 的寄存器（算到一半的结果、程序计数器 PC 等）必须存进 TCB，否则下次回来状态就全部丢失了（因为寄存器只有一套）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态标记&lt;/strong&gt;：系统需要记录谁在 Running，谁在 Waiting，谁已经 Ready。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;3.  终极进化：以人为本 —— 分时系统与进程&lt;a href=&quot;#3--终极进化以人为本--分时系统与进程&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;












&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;时间背景&lt;/th&gt;&lt;th&gt;代表&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1960s 晚期 - 1970&lt;/td&gt;&lt;td&gt;Multics, Unix&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;到了 60 年代末，计算机不再仅仅用于科学计算，人们开始通过终端（Teletype/屏幕/键盘）与计算机交互。如果继续沿用多道程序设计的逻辑（只有 I/O 阻塞才切换），会发生什么？&lt;/p&gt;&lt;p&gt;假设 Job A 中写了一个死循环 &lt;code&gt;while(1)&lt;/code&gt; 且不进行 I/O，它将永远霸占 CPU。坐在终端前的用户 B 敲下键盘，屏幕毫无反应。这对于交互式系统是不可接受的。&lt;/p&gt;&lt;p&gt;为了解决 “响应时间太慢，用户体验差” 的矛盾，MIT、贝尔实验室和通用电气联合开发了 &lt;a href=&quot;https://en.wikipedia.org/wiki/Multics&quot; target=&quot;_blank&quot;&gt;Multics&lt;/a&gt;（虽然它过于复杂，但其思想催生了后来的 &lt;a href=&quot;https://en.wikipedia.org/wiki/Unix&quot; target=&quot;_blank&quot;&gt;Unix&lt;/a&gt;）。&lt;/p&gt;&lt;p&gt;系统引入了&lt;strong&gt;硬件定时器&lt;/strong&gt;。这一改变是革命性的：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;抢占式调度&lt;/strong&gt;（Preemptive）：无论程序 A 是否运行完，每隔几十毫秒（一个时间片），操作系统强制打断它，保存上下文，把 CPU 扔给程序 B。&lt;/li&gt;
&lt;li&gt;幻觉：这种快速的切换，让每个用户都产生了一种 “我独占了一台电脑” 的错觉。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/Round_Robin_Schedule_Example.jpg&quot; alt=&quot;轮转调度算法 (Round-Robin) 时间片轮转示意&quot; /&gt;&lt;figcaption&gt;轮转调度算法 (Round-Robin) 时间片轮转示意&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;也就是在这个时期，&lt;strong&gt;进程&lt;/strong&gt;这个概念被正式确立，并赋予了现代含义。为了维持这种 “独占的幻觉”，操作系统构建了两道高墙：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;时间的虚拟化：通过保存/回复 CPU 寄存器上下文，让每个进程觉得拥有独立的 CPU。&lt;/li&gt;
&lt;li&gt;空间的虚拟化：通过 MMU（内存管理的单元）和页表，让每个进程觉得拥有独立的、连续的内存空间（虚拟内存）。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;至此，进程不再仅仅是用户的代码，它是：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;代码&lt;/strong&gt; + &lt;strong&gt;动态执行上下文&lt;/strong&gt;（某一时刻寄存器中的值）+ &lt;strong&gt;虚拟地址空间&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;本文参考了以下资料，排名无先后顺序：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Timeline_of_operating_systems&quot; target=&quot;_blank&quot;&gt;Timeline of operating systems - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/History_of_operating_systems&quot; target=&quot;_blank&quot;&gt;History of operating systems - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/ENIAC&quot; target=&quot;_blank&quot;&gt;ENIAC - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/John_von_Neumann&quot; target=&quot;_blank&quot;&gt;John von Neumann - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/First_Draft_of_a_Report_on_the_EDVAC&quot; target=&quot;_blank&quot;&gt;First Draft of a Report on the EDVAC - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/GM-NAA_I/O&quot; target=&quot;_blank&quot;&gt;GM-NAA I/O - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Resident_monitor&quot; target=&quot;_blank&quot;&gt;Resident monitor - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Multics&quot; target=&quot;_blank&quot;&gt;Multics - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Unix&quot; target=&quot;_blank&quot;&gt;Unix - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://byjus.com/gate/types-of-operating-system-notes/&quot; target=&quot;_blank&quot;&gt;Types of Operating Systems - BYJU’S&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bitsavers.org/pdf/ibm/360/os/R21.7_Apr73/GC28-6628-9_OS_System_Ctl_Blks_R21.7_Apr73.pdf&quot; target=&quot;_blank&quot;&gt;IBM OS/360 System Control Blocks (PDF)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Task_Control_Block&quot; target=&quot;_blank&quot;&gt;Task Control Block - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Round-robin_scheduling&quot; target=&quot;_blank&quot;&gt;Round-robin scheduling - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;</content:encoded></item><item><title>C++ 中的初始化列表和列表初始化</title><link>https://bfmhno3.github.io/posts/initialization-in-cpp/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/initialization-in-cpp/</guid><description>深入理解 C++ 中初始化列表与列表初始化的区别：前者解决对象生命周期与内存模型问题，后者统一初始化语法并提供类型安全保障。</description><pubDate>Sat, 03 Jan 2026 15:07:00 GMT</pubDate><content:encoded>&lt;p&gt;很多初学者容易混淆&lt;strong&gt;初始化列表&lt;/strong&gt;（Member Initializer List）和&lt;strong&gt;列表初始化&lt;/strong&gt;（List Initialization），因为它们的名字很像，但它们实际上解决的是完全不同的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;初始化列表&lt;/strong&gt;：解决的是&lt;strong&gt;对象生命周期与内存模型&lt;/strong&gt;的问题（“什么时候赋初值”）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;列表初始化&lt;/strong&gt;：解决的是&lt;strong&gt;类型系统的统一性与安全性&lt;/strong&gt;的问题（“用什么语法赋初值”）&lt;/li&gt;
&lt;/ol&gt;
&lt;section&gt;&lt;h2&gt;初始化列表（Member Initializer List）&lt;a href=&quot;#初始化列表member-initializer-list&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;它的形式出现在构造函数参数列表之后，函数体大括号之前，以 &lt;code&gt;:&lt;/code&gt; 开头。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;span&gt; // 初始化列表&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;section&gt;&lt;h3&gt;初始化 vs. 赋值&lt;a href=&quot;#初始化-vs-赋值&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在 C++ 的对象模型中，“初始化”（Initialization）和 “赋值”（Assignment）是两个截然不同的物理过程。&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;内存分配&lt;/strong&gt;（&lt;em&gt;Allocation&lt;/em&gt;）：在栈上或堆上划出一块内存&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;初始化&lt;/strong&gt;（&lt;em&gt;Initialization&lt;/em&gt;）：在这块内存上构建对象，使其成为一个合法的实例&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;赋值&lt;/strong&gt;（&lt;em&gt;Assignment&lt;/em&gt;）：对象已经存在了，擦出旧值，填入新值。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;构造函数的执行时间线：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;进入构造函数之前：编译器必须确保所有成员变量都已经 “出生”（初始化完成）&lt;/li&gt;
&lt;li&gt;进入构造函数体（&lt;code&gt;{...}&lt;/code&gt;）：这已经是 “出生后” 的世界了，这里面写的代码都是 “赋值” 操作。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;为什么要用初始化列表？&lt;a href=&quot;#为什么要用初始化列表&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;如果你不用初始化列表，而是写在函数体内：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Person&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 写法 1：在函数体内赋值&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Person&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;底层发生了什么？&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;隐式初始化：在进入 &lt;code&gt;{&lt;/code&gt; 之前，编译器发现 &lt;code&gt;name&lt;/code&gt; 还没有初始化，于是强行用 &lt;code&gt;std::string&lt;/code&gt; 的默认构造函数。&lt;code&gt;name&lt;/code&gt; 变成了一个空字符串 &lt;code&gt;&quot;&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;赋值操作&lt;/strong&gt;：进入 &lt;code&gt;{&lt;/code&gt; 后，执行 &lt;code&gt;name = n;&lt;/code&gt;。调用 &lt;code&gt;std::string&lt;/code&gt; 的赋值运算符，把刚才那个空字符串的内容丢弃，拷贝 &lt;code&gt;n&lt;/code&gt; 的内容&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;这就类似于，你先建了一个空房子，然后立即把它拆了重建成你想要的样子，这就是双倍的开销。&lt;/p&gt;&lt;p&gt;正确的写法（初始化列表）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 写法 2：使用初始化列表&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Person&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;底层发生了什么？&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;直接构造：直接调用 &lt;code&gt;std::string&lt;/code&gt; 的拷贝构造函数，用 &lt;code&gt;n&lt;/code&gt; 来 “生出” &lt;code&gt;name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;函数体为空，无操作。收益：省去了一次默认构造和一次赋值操作。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;必须使用的场景（物理限制）&lt;a href=&quot;#必须使用的场景物理限制&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;有些东西必须 “出生时” 就确定，生出来后再改就晚了。这些情况必须使用初始化列表：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;const&lt;/code&gt; 成员：常量一旦出生就不能修改（不能赋值）&lt;/li&gt;
&lt;li&gt;引用成员（&lt;code&gt;&amp;amp;&lt;/code&gt;）：引用一旦出生必须绑定到一个对象，不能重新绑定（不能赋值）&lt;/li&gt;
&lt;li&gt;没有默认构造函数的类成员：如果成员是一个类，且它没有默认构造函数（必须带参），编译器无法在进入函数体前 “隐式初始化” 它，必须显式指定&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;初始化的顺序（内存布局决定）&lt;a href=&quot;#初始化的顺序内存布局决定&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;初始化列表中的顺序并不决定初始化的真实顺序！&lt;strong&gt;成员变量的初始化顺序严格由它们在类定义（Class Definition）中声明的顺序决定&lt;/strong&gt;。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 错误示范！看起来像先算 y，其实 x 会先初始化，次数 y 是垃圾值&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;C++ 对象的内存布局在编译器就确定了（&lt;code&gt;x&lt;/code&gt; 在前，&lt;code&gt;y&lt;/code&gt; 在后）。析构时必须按相反顺序销毁。为了保证构造和析构的对称性（LIFO），构造顺序必须固定，不能随你怎么写列表儿改变。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;列表初始化（List Initialization）&lt;a href=&quot;#列表初始化list-initialization&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;它的形式是使用花括号 &lt;code&gt;{}&lt;/code&gt;。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;section&gt;&lt;h3&gt;统一初始化语义&lt;a href=&quot;#统一初始化语义&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在 C++11 之前，初始化的语法简直是精神分裂：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Point&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;C++11 引入 &lt;code&gt;{}&lt;/code&gt; 旨在&lt;strong&gt;统一初始化&lt;/strong&gt;（&lt;em&gt;Uniform Inialization&lt;/em&gt;）：万物皆可 &lt;code&gt;{}&lt;/code&gt; 初始化。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;底层机制：&lt;code&gt;std::initializer_list&lt;/code&gt;&lt;a href=&quot;#底层机制stdinitializer_list&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;当你用&lt;code&gt;{1, 2, 3, 4}&lt;/code&gt; 初始化一个容器（如 &lt;code&gt;std::vector&lt;/code&gt;）时，发生了什么？&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;编译器魔法：编译器会在静态存储区悄悄创建一个数组&lt;/li&gt;
&lt;li&gt;包装：编译器构建一个 &lt;code&gt;std::initializer_list&amp;lt;T&amp;gt;&lt;/code&gt; 对象。这其实是一个轻量级的 “视图” 或 “胖指针”，它只包含两个指针：一个指向数组开头，一个指向结尾（或长度）&lt;/li&gt;
&lt;li&gt;传递：这个轻量级对象被传给 &lt;code&gt;std::vector&lt;/code&gt; 的构造函数&lt;/li&gt;
&lt;li&gt;消费：&lt;code&gt;vector&lt;/code&gt; 遍历这个 list，把数据拷贝到自己的内存里&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;这就是为什么你可以给 &lt;code&gt;std::vector&lt;/code&gt; 赛任意数量的初始值。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;安全特性：防止窄化转换（Narrowing Conversion）&lt;a href=&quot;#安全特性防止窄化转换narrowing-conversion&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是一个类型安全设计。初始化意味着 “建立一个合法的初始状态”。如果数据在初始化过程中丢失了精度，那么这个初始状态本身就是 “不诚实” 的。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3.14&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; // C++98: 允许 a 变成 3（截断）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;3.14&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt; // C++11: 编译器报错！禁止数据丢失&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;{}&lt;/code&gt; 语法强制要求：如果源类型的值无法无损地放入目标类型，编译器必须报错。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;解决 “最令人头秃的解析”（Most Vexing Parse）&lt;a href=&quot;#解决-最令人头秃的解析most-vexing-parse&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;C++ 的语法歧义：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 你的意图：创建一个对象 a，调用默认构造函数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;编译器的理解：声明了一个函数 &lt;code&gt;a()&lt;/code&gt;，它不接受参数，返回类型是 &lt;code&gt;A&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;解决办法：使用 &lt;code&gt;{}&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;{};&lt;/span&gt;&lt;span&gt; // 明确无误，这就是创建对象&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;因为函数声明不可能用 &lt;code&gt;{}&lt;/code&gt; 结尾。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;两者的 “撞车”&lt;a href=&quot;#两者的-撞车&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;当一个类既有普通构造函数，又有接受 &lt;code&gt;std::initializer_list&lt;/code&gt; 的构造函数时，&lt;code&gt;{}&lt;/code&gt; 会极度优先匹配后者。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Magic&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Magic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 构造函数 1：普通构造&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Normal Constructor&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;endl&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Magic&lt;/span&gt;&lt;span&gt;(std::&lt;/span&gt;&lt;span&gt;initializer_list&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 构造函数 2：列表构造&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;cout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;List Constructor&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;endl&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Magic&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;m1&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; // 调用构造函数 1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Magic&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;m2&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt; // 调用构造函数 2（因为 {10, 20} 被视为列表）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;如何选择 &lt;code&gt;{}&lt;/code&gt; 与 &lt;code&gt;()&lt;/code&gt;？&lt;a href=&quot;#如何选择--与-&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;{}&lt;/code&gt; 的写法&lt;a href=&quot;#-的写法&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;section&gt;&lt;h4&gt;直接列表初始化（Direct List Initialization）&lt;a href=&quot;#直接列表初始化direct-list-initialization&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;特征：不带等号 &lt;code&gt;=&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;心智模型：我非常确定我要构造这个对象，请直接用这些参数造出来，不许做任何隐式类型转换。&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 1. 普通变量&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt;                       // x 初始化为 0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;3.14&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt;                 // d 初始化为 3.14&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 2. 类对象&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt;    // 容器包含 1, 2, 3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Person&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt;          // 调用 Person(string, int)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 3. 堆内存 new&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt;          // 申请内存并初始化为 5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 4. 临时对象（匿名对象）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Person&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;Bob&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 5. 基类与成员初始化&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Derive&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;Base&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;m_val&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;} {}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;拷贝列表初始化（Copy List Initialization）&lt;a href=&quot;#拷贝列表初始化copy-list-initialization&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;特征：带有等号 &lt;code&gt;=&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;心智模型：先把右边的花括号转换成目标类型，然后再拷贝（或移动）给左边&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 1. 变量定义&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 2. 函数传参（隐式构造）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;foo&lt;/span&gt;&lt;span&gt;(std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;foo&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;span&gt;                 // 编译器隐式地把 {1, 2, 3} 转换成了 vector&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 3. 函数返回值&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;bar&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt;           // 隐式构造并返回&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;直接列表初始化和拷贝列表初始化的选择&lt;a href=&quot;#直接列表初始化和拷贝列表初始化的选择&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;在绝大多数情况下，由于现代编译器的优化，拷贝列表初始化会被直接优化成为直接列表初始化，这被称为&lt;strong&gt;复制省略&lt;/strong&gt;（&lt;em&gt;Copy Elision&lt;/em&gt;）。&lt;/p&gt;&lt;p&gt;但是存在一种例外情况，那就是 &lt;code&gt;explicit&lt;/code&gt; 关键字声明的构造函数。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;&lt;code&gt;explicit&lt;/code&gt; 关键字的影响&lt;a href=&quot;#explicit-关键字的影响&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;如果一个类的构造函数被声明为 &lt;code&gt;explicit&lt;/code&gt;（显式），那么第 2 种写法（带等号的会被禁止）。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Widget&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;explicit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Widget&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Widget&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;w1&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt;      // OK：直接初始化，显式调用&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Widget&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;w2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt;   // Error：拷贝列表初始化要求 “隐式转换”，但被 explicit 阻止了&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;所以我们可以得出结论：&lt;code&gt;Type x{...}&lt;/code&gt; 比 &lt;code&gt;Type x = { ... }&lt;/code&gt; 更加通用，因为它不受 &lt;code&gt;explicit&lt;/code&gt; 的限制，更推荐使用不写等号的写法。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;真的应该所有情况都用 &lt;code&gt;{}&lt;/code&gt; 吗？&lt;a href=&quot;#真的应该所有情况都用--吗&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;虽然 C++ Core Guidenlines 推荐默认使用 &lt;code&gt;{}&lt;/code&gt;，但你必须警惕两个巨大的陷阱。这两个陷阱的根源在于编译器对 &lt;code&gt;std::initializer_list&lt;/code&gt; 的极度偏爱。&lt;/p&gt;&lt;section&gt;&lt;h5&gt;陷阱 1：&lt;code&gt;std::vector&lt;/code&gt; 的构造&lt;a href=&quot;#陷阱-1stdvector-的构造&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;v1&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; // 用 ()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;v2&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt; // 用 {}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;解析：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;v1&lt;/code&gt;（圆括号）：调用 &lt;code&gt;vectore(size_type n, const T&amp;amp; val)&lt;/code&gt;，创建了 10 个元素，每个元素都是 5&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v2&lt;/code&gt;（花括号）：编译器看到 &lt;code&gt;{}&lt;/code&gt;，优先查找是否接受 &lt;code&gt;std::initializer_list&lt;/code&gt; 的构造函数，找到了，于是创建了 2 个元素，分别是 10 和 5&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;当构造函数发生重载时，如果存在接受 &lt;code&gt;std::intitalizer_list&lt;/code&gt; 的版本，编译器会贪婪地优先匹配它，哪怕需要进行一些类型提升。只有完全匹配不上了，才会退回去找普通的构造函数。&lt;/p&gt;&lt;p&gt;建议：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;原则上：默认用 &lt;code&gt;{}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;例外：当在调用容器的大小/数量构造函数时，必须用 &lt;code&gt;()&lt;/code&gt;，否则会被误解为 “元素内容”&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h5&gt;陷阱 2：&lt;code&gt;auto&lt;/code&gt; 类型推导的歧义&lt;a href=&quot;#陷阱-2auto-类型推导的歧义&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;&lt;p&gt;这一点在 C++11/14 和 C++17 中表现不同，非常令人困惑。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// x 的类型是什么？是 int 吗？&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// NO! x 的类型是 std::initializer_list&amp;lt;int&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如果你本意是想要一个 &lt;code&gt;int&lt;/code&gt;，结果得到了一个轻量级列表对象，后面的代码可能会出问题。&lt;/p&gt;&lt;p&gt;修正写法（C++17 起）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt;      // C++17 规定推导为 int&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt;   // 推导为 std::initializer_list&amp;lt;int&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;建议：配合 &lt;code&gt;auto&lt;/code&gt; 使用 &lt;code&gt;{}&lt;/code&gt; 时要格外小心，最好明确类型，或者确保你真的想要 &lt;code&gt;std::initializer_list&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;{}&lt;/code&gt; 的特性：默认值初始化（Value Initialization）&lt;a href=&quot;#-的特性默认值初始化value-initialization&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是 &lt;code&gt;{}&lt;/code&gt; 最让我觉得 “舒服” 的地方。&lt;/p&gt;&lt;p&gt;如果你想把一个对象清零，或者初始化为 “空状态”：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 旧写法&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;      // 危险！如果是局部变量，i 的值是随机的（垃圾值）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;     // 危险！野指针&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 新写法（空花括号）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;{};&lt;/span&gt;&lt;span&gt;    // 安全！保证初始化为 0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;{};&lt;/span&gt;&lt;span&gt;   // 安全！保证初始化为 nullptr&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;{};&lt;/span&gt;&lt;span&gt; // 安全！保证初始化为 0.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;空的 &lt;code&gt;{}&lt;/code&gt; 触发&lt;strong&gt;值初始化&lt;/strong&gt;（&lt;em&gt;Value Initialization&lt;/em&gt;）。对于内置类型（&lt;code&gt;int&lt;/code&gt;、&lt;code&gt;float&lt;/code&gt;、指针），它会执行&lt;strong&gt;零初始化&lt;/strong&gt;（&lt;em&gt;Zero Initialization&lt;/em&gt;）。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>C++ 中的构造函数</title><link>https://bfmhno3.github.io/posts/constructor-in-cpp/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/constructor-in-cpp/</guid><description>深入理解 C++ 构造函数的本质：从资源管理到性能优化。涵盖默认构造、拷贝构造、移动构造，以及 RAII、explicit、constexpr 等关键机制。</description><pubDate>Sat, 03 Jan 2026 13:05:00 GMT</pubDate><content:encoded>&lt;p&gt;对于 C++ 对象而言，我们认为：对象 = 内存 + 语义（不变量）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内存：仅仅是电子与硅晶体中状态未知的比特位。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语义&lt;/strong&gt;：这段内存代表什么含义（是 &lt;code&gt;int&lt;/code&gt;、是 &lt;code&gt;char&lt;/code&gt; 还是 &lt;code&gt;float&lt;/code&gt;），以及它必须满足的条件（“不变量”，Invariant）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;构造函数&lt;/strong&gt;（&lt;em&gt;Constructor&lt;/em&gt;）的本质就是将 “原始、混沌” 的内存强制转换为 “持有特定语义的、合法的对象” 的原子操作过程。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;核心逻辑&lt;a href=&quot;#核心逻辑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在 C 语言中，创建一个 &lt;code&gt;struct&lt;/code&gt; 通常分为两步：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;分配内存（&lt;code&gt;malloc&lt;/code&gt; 或栈上声明）&lt;/li&gt;
&lt;li&gt;赋值（&lt;code&gt;init&lt;/code&gt; 函数或手动赋值）&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;问题在于：如果在第 1 步和第 2 步之间使用该对象，就会导致灾难（未定义行为）。或者，如果使用者忘记了第 2 步，系统就会处于 “非法状态”。&lt;/p&gt;&lt;p&gt;C++ 引入构造函数就是为了保证：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;如果一个对象存在，那么它一定是合法的。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;构造函数保证了&lt;strong&gt;初始化&lt;/strong&gt;（&lt;em&gt;Initialization&lt;/em&gt;）与&lt;strong&gt;定义&lt;/strong&gt;（&lt;em&gt;Defination&lt;/em&gt;）的不可分割性。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;构造函数的执行流&lt;a href=&quot;#构造函数的执行流&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;当你写下 &lt;code&gt;T object(args);&lt;/code&gt; 时，编译器实际执行了以下步骤：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;分配内存&lt;/strong&gt;：在栈或堆上找到一块足够容纳 &lt;code&gt;sizeof(T)&lt;/code&gt; 的空间。此时，内存里的数据是随机的（Garbage）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行初始化列表&lt;/strong&gt;（Initialization List）：这是真正的初始化时刻。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行函数体&lt;/strong&gt;（Function Body）：这实际上是后续的计算或赋值操作，而非初始化。&lt;/li&gt;
&lt;/ol&gt;&lt;section&gt;&lt;h3&gt;为什么首选初始化列表？&lt;a href=&quot;#为什么首选初始化列表&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;因为 C++ 规定成员变量在进入构造函数体 &lt;code&gt;{}&lt;/code&gt; 之前必须完成构建。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Class&lt;/span&gt;&lt;span&gt;() : &lt;/span&gt;&lt;span&gt;member&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;span&gt; // 直接在内存位置上构造 member&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;使用初始化列表的成本仅为 1 次构造。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Class&lt;/span&gt;&lt;span&gt;() { &lt;/span&gt;&lt;span&gt;member&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;过程：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;调用 &lt;code&gt;member&lt;/code&gt; 的默认构造函数（无参）。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;member&lt;/code&gt; 的赋值运算符 &lt;code&gt;operator=&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;在这个过程中的成本为：1 次构造 + 1 次赋值（还可能设计旧内存释放和新内存申请）。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;初始化列表不仅是效率优化，对于 &lt;code&gt;const&lt;/code&gt; 成员或 &lt;code&gt;reference&lt;/code&gt;（引用）成员，它是&lt;strong&gt;唯一&lt;/strong&gt;的初始化方式，因为它们创建后不可修改（不可赋值）。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;构造函数的分类&lt;a href=&quot;#构造函数的分类&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;根据对象资源管理的不同需求，构造函数演化出了四种主要形态。我们将用&lt;strong&gt;资源所有权&lt;/strong&gt;的视角来区分它们。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;默认构造函数（Default Constructor）&lt;a href=&quot;#默认构造函数default-constructor&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;语义：无中生有&lt;/li&gt;
&lt;li&gt;形式：&lt;code&gt;T()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;视角：当对象被创建但外界未提供任何信息时，对象应处于什么状态？通常是 “空状态” 或 “零状态”&lt;/li&gt;
&lt;li&gt;注意：如果类中包含原始指针，编译器生成的默认构造函数不会置空指针（由于 C 的遗留包袱），这会导致悬垂指针。因此现代 C++ 提倡显式定义或使用成员默认初始化（&lt;code&gt;int* p = nullptr;&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;参数化构造函数（Parameterized Constructor）&lt;a href=&quot;#参数化构造函数parameterized-constructor&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;语义：根据蓝图定制&lt;/li&gt;
&lt;li&gt;形式：&lt;code&gt;T(args...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;视角：将外部数据约束映射到内部不变量。例如，创建 “圆” 对象，参数是半径。构造函数必须检查 &lt;code&gt;radius &amp;gt; 0&lt;/code&gt;，这就是维护 “不变量”&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;拷贝构造函数（Copy Constructor）&lt;a href=&quot;#拷贝构造函数copy-constructor&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;语义：复制（细胞分裂、克隆）&lt;/li&gt;
&lt;li&gt;形式：&lt;code&gt;T(const T&amp;amp; other)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;视角：
&lt;ul&gt;
&lt;li&gt;如果对象时&lt;strong&gt;值语义&lt;/strong&gt;（如整数、坐标），直接按位拷贝（Shallow Copy）&lt;/li&gt;
&lt;li&gt;如果对象持有&lt;strong&gt;资源&lt;/strong&gt;（如堆内存指针、文件句柄），必须进行&lt;strong&gt;深拷贝&lt;/strong&gt;（&lt;em&gt;Deep Copy&lt;/em&gt;）&lt;/li&gt;
&lt;li&gt;本质矛盾：如果只复制指针，两个对象指向同一块内存，析构时会发生 “Double Free” 错误。因此拷贝构造函数必须重新分配资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;移动构造函数（Move Constructor）&lt;a href=&quot;#移动构造函数move-constructor&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;移动构造函数是 C++11 提出的革命性进步。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;语义：所有权转移（器官移植）&lt;/li&gt;
&lt;li&gt;形式：&lt;code&gt;T(T&amp;amp;&amp;amp; other)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;视角：
&lt;ul&gt;
&lt;li&gt;在 C++98 中，如果要将一个临时对象（即将销毁）放入容器，比如先复制再销毁。这极度浪费性能（如复制一个巨大的 &lt;code&gt;std::vector&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;移动构造函数利用&lt;strong&gt;右值引用&lt;/strong&gt;（&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;），识别出 &lt;code&gt;other&lt;/code&gt; 是一个即将消亡的对象。&lt;/li&gt;
&lt;li&gt;它&lt;strong&gt;偷走&lt;/strong&gt; &lt;code&gt;other&lt;/code&gt; 的资源（指针指向新主，旧指针置空），而非复制数据&lt;/li&gt;
&lt;li&gt;代价：极低（仅是指针赋值）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;关键机制与陷阱&lt;a href=&quot;#关键机制与陷阱&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;explicit&lt;/code&gt; 关键字：拒绝隐式转换&lt;a href=&quot;#explicit-关键字拒绝隐式转换&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;C++ 默认允许单参数构造函数进行隐式类型转换。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Buffer&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;Buffer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;) { ... } };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Buffer&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;42&lt;/span&gt;&lt;span&gt;)；&lt;/span&gt;&lt;span&gt; // 编译器偷偷执行了 Buffer(42)，可能并不是你想要的&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;从安全角度（Safety First）出发，隐式类型转换破坏了强类型系统。标记 &lt;code&gt;explicit&lt;/code&gt; 禁止这种 “自作聪明” 的行为，强制显式调用。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;委托构造（Delegating Constructors）&lt;a href=&quot;#委托构造delegating-constructors&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;允许一个构造函数调用同类的另一个构造函数。这是为了准许 &lt;strong&gt;DRY&lt;/strong&gt;（&lt;em&gt;Don’t Repeat Yourself&lt;/em&gt;）原则，防止初始化逻辑碎片化。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;构造与虚函数&lt;a href=&quot;#构造与虚函数&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;永远不要在构造函数中调用虚函数&lt;/strong&gt;。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;原理：在基类构造期间，派生类的部分尚未初始化。为了安全，C++ 此时将对象视为基类类型。虚函数表（&lt;code&gt;vtalbe&lt;/code&gt;）指针指向基类表，多态失效。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;RAII 与构造函数&lt;a href=&quot;#raii-与构造函数&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;将上述所有内容串联起来的概念就是 RAII（&lt;em&gt;Resource Acquisition Is Initialization&lt;/em&gt;），这是 C++ 的灵魂。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;资源获取即初始化&lt;/strong&gt;：资源的生命周期严格绑定对象的生命周期&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构造函数&lt;/strong&gt;：资源的获取点（锁住互斥量、打开文件、分配内存）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;析构函数&lt;/strong&gt;：资源的释放点（解锁、关闭、释放）&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;C++ 的构造函数不仅仅是用来 “赋值” 的函数，它是类型系统安全性的守门人，是资源管理自动化的起点。&lt;/p&gt;&lt;p&gt;掌握构造函数，不仅仅是记住语法，而是要时刻思考：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;这个对象诞生的一瞬间，我如何保证它拥有了所需的资源，且处于绝对合法的状态？&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;相关关键字&lt;a href=&quot;#相关关键字&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;控制编译器行为&lt;a href=&quot;#控制编译器行为&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;C++ 编译器通常会 “自作聪明” 地为你生成默认构造、拷贝构造等。以下关键字则可以用于&lt;strong&gt;精确控制&lt;/strong&gt;这种自动行为。&lt;/p&gt;&lt;section&gt;&lt;h4&gt;&lt;code&gt;= default&lt;/code&gt;&lt;a href=&quot;#-default&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;语义：出厂设置&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;当你手写了一个参数化构造函数 &lt;code&gt;T(int a)&lt;/code&gt; 后，编译器认为你是一个有主见的人，于是不再自动生成无参的默认构造函数 &lt;code&gt;T()&lt;/code&gt;。如果此时你又想要那个 “空” 的默认构造函数，不需要再手写个空函数体 &lt;code&gt;{}&lt;/code&gt;（这会导致它变成 “用户提供的”，从而失去某些 trivial/POD 特性），直接用 &lt;code&gt;= default&lt;/code&gt; 让编译器恢复它的默认生成逻辑。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Example&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Example&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;span&gt;         // 自定义构造&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Example&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;    // 强制找回默认构造，且比手写 {} 更高效&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;&lt;code&gt;= delete&lt;/code&gt;（C++11）&lt;a href=&quot;#-deletec11&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;语义：此路不通&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;有些对象在语义上是&lt;strong&gt;独一无二&lt;/strong&gt;的（例如：单例模式、硬件驱动句柄 &lt;code&gt;Mutex&lt;/code&gt;、&lt;code&gt;FileStream&lt;/code&gt;），它们&lt;strong&gt;绝不能&lt;/strong&gt;被拷贝。&lt;/p&gt;&lt;p&gt;在 C++11 之前，我们通过把拷贝构造函数设为 &lt;code&gt;private&lt;/code&gt; 来防止拷贝。C++11 之后，可以直接在&lt;strong&gt;语法层面&lt;/strong&gt; “删除” 这个函数的存在。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Mutex&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 任何尝试拷贝代码的操作，在编译期间就会报错&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Mutex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Mutex&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Mutex&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;operator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Mutex&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;&lt;code&gt;using&lt;/code&gt;（继承构造函数）&lt;a href=&quot;#using继承构造函数&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;语义：拿来主义&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;派生类通常不会继承基类的构造函数。如果基类有 10 种构造方式，派生类想支持同样的 10 种，以前得手动写 10 个转发函数。&lt;/p&gt;&lt;p&gt;&lt;code&gt;using&lt;/code&gt; 关键字告诉编译器：把基类的构造函数直接 “引入” 到当前作用域。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Base&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Base&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;Base&lt;/span&gt;&lt;span&gt;(std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;Base&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Derived&lt;/span&gt;&lt;span&gt;: Base {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; Base::&lt;/span&gt;&lt;span&gt;Base&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; // 一句话，拥有了上述三种构造方式&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;性能优化&lt;a href=&quot;#性能优化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这部分关键字主要服务于嵌入式开发和高性能计算，通过向编译器提供更多信息来优化机器码。&lt;/p&gt;&lt;section&gt;&lt;h4&gt;&lt;code&gt;noexcept&lt;/code&gt;&lt;a href=&quot;#noexcept&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;语义：我保证不惹麻烦（不抛出异常）&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这是&lt;strong&gt;移动语义&lt;/strong&gt;（Move Semantics）生效的关键。当 &lt;code&gt;std::vector&lt;/code&gt; 扩容时，它需要把旧数据搬到新内存。如果你的移动构造函数没有标记 &lt;code&gt;noexcept&lt;/code&gt;，&lt;code&gt;std::vector&lt;/code&gt; 为了内存安全（怕搬到一半抛异常，导致旧数据没了，新数据也没好），会放弃移动，强行降级为拷贝。&lt;/p&gt;&lt;p&gt;这在大数据量或高性能要求场景下会带来极大的损耗。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BigData&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 承诺：移动操作绝不会失败，编译器看到这个才会大胆优化&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;BigData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BigData&lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;other&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;noexcept&lt;/span&gt;&lt;span&gt; { ... }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;&lt;code&gt;constexpr&lt;/code&gt;（C++11/14）&lt;a href=&quot;#constexprc1114&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;语义：在编译时就已经准备好了&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果一个对象的构造参数在编译时就是确定的常量，那么为什么要等到程序运行（Runtime）才去分配内存、赋值呢？&lt;/p&gt;&lt;p&gt;&lt;code&gt;constexpr&lt;/code&gt; 构造函数允许编译器在&lt;strong&gt;编译阶段&lt;/strong&gt;就计算出对象的内存布局，并直接烧录在二进制文件的只读数据端（&lt;code&gt;.rodata&lt;/code&gt;）或直接作为立即数嵌入指令中。&lt;/p&gt;&lt;p&gt;这对于&lt;strong&gt;嵌入式系统&lt;/strong&gt;（节省运行时开销、Flash/RAM 布局）至关重要。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Point&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;constexpr&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Point&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_y&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_x&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_y&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 编译后，p 甚至可能不存在，直接被优化为立即数操作&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;constexpr&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Point&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;逻辑控制与异常处理&lt;a href=&quot;#逻辑控制与异常处理&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;section&gt;&lt;h4&gt;&lt;code&gt;explicit&lt;/code&gt;&lt;a href=&quot;#explicit&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;在前文已经讲到。同时，除了单参数构造函数，&lt;strong&gt;多参数构造函数&lt;/strong&gt;（C++11 列表初始化）也需要注意。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;struct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Vector3&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;explicit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Vector3&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Vector3&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3.0&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;span&gt; // 错误！因为 explicit 禁止了 {list} -&amp;gt; Object 的隐式类型转换&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Vector3&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3.0&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;span&gt; // 正确，显式调用&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;&lt;code&gt;try&lt;/code&gt;（Function-try block）&lt;a href=&quot;#tryfunction-try-block&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;语义：在进入内部前就能捕获错误&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;构造函数分两步：初始化列表 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 函数体。如果在&lt;strong&gt;初始化列表&lt;/strong&gt;阶段（比如基类构造、成员对象构造）抛出了异常，普通的 &lt;code&gt;try-catch&lt;/code&gt; 包裹函数体是&lt;strong&gt;抓不住&lt;/strong&gt;的。必须把 &lt;code&gt;try&lt;/code&gt; 写在函数体外，这就是&lt;strong&gt;函数 try 块&lt;/strong&gt;。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ResourceManager&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;core_resource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;core&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ... 函数体&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (...) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 能够捕获 core_resource 初始化时抛出的异常&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 注意：构造函数里的 catch 必定会再次抛出异常，因为对象构造函数失败了，必须通知外界&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>C++ 中的 list</title><link>https://bfmhno3.github.io/posts/list-in-cpp/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/list-in-cpp/</guid><description>深入理解 C++ 中的 std::list 双向链表容器，探讨其底层原理、独有优势（如头部操作、接合等）、迭代器特性，以及与 std::vector 的选择权衡。</description><pubDate>Sat, 03 Jan 2026 13:05:00 GMT</pubDate><content:encoded>&lt;p&gt;在现代 C++ 开发中，虽然 &lt;code&gt;std::vector&lt;/code&gt; 足以应付绝大多数的场景，但是在某些特定场景下，&lt;code&gt;std::list&lt;/code&gt; 依旧是不可替代的神器。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;核心概念与底层原理&lt;a href=&quot;#核心概念与底层原理&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;头文件：&lt;code&gt;#include &amp;lt;list&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;本质：&lt;strong&gt;双向链表&lt;/strong&gt;（Doubly Linked List）&lt;/li&gt;
&lt;li&gt;内存模型：&lt;strong&gt;非连续内存&lt;/strong&gt;。每个元素（节点）都是独立分配在堆上的，节点之间通过指针（&lt;code&gt;prev&lt;/code&gt; 和 &lt;code&gt;next&lt;/code&gt;）连接&lt;/li&gt;
&lt;li&gt;特点：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不支持随机访问&lt;/strong&gt;：不能使用下标 &lt;code&gt;l[5]&lt;/code&gt; 访问元素，必须从头一个一个遍历过去&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;插入 / 删除极快&lt;/strong&gt;：只要持有了某个位置的迭代器，在该位置插入或删除元素的操作是 &lt;span&gt;&lt;span&gt;O(1)O(1)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的，且&lt;strong&gt;不需要移动其他元素&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;初始化与构造&lt;a href=&quot;#初始化与构造&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;用法与 &lt;code&gt;std::vector&lt;/code&gt; 非常相似：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;list&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;l1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;              // 空链表&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;l2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt;  // 列表初始化&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;l3&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;l2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt;          // 拷贝构造&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;l4&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt;      // 5 个 元素，全是 100&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;独有的操作优势（&lt;code&gt;std::vector&lt;/code&gt; 做不到的）&lt;a href=&quot;#独有的操作优势stdvector-做不到的&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这是 &lt;code&gt;std::list&lt;/code&gt; 存在的理由。由于它是链表，它支持一些操作极其高效，或者提供了 &lt;code&gt;std::vector&lt;/code&gt; 根本没有的接口。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;头部操作&lt;a href=&quot;#头部操作&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;std::vector&lt;/code&gt; 在头部插入 / 删除操作极其低效（&lt;span&gt;&lt;span&gt;O(N)O(N)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;），而 &lt;code&gt;std::list&lt;/code&gt; 则是瞬间完成（&lt;span&gt;&lt;span&gt;O(1)O(1)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;）。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;push_front(val)&lt;/code&gt;：头部插入&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pop_front()&lt;/code&gt;：头部删除&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;接合（Splicing）&lt;a href=&quot;#接合splicing&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;可以将一个 &lt;code&gt;std::list&lt;/code&gt; 的元素（全部或部分）直接 “剪切” 并 “粘贴” 到另一个 &lt;code&gt;std::list&lt;/code&gt; 中，这是&lt;strong&gt;纯指针操作&lt;/strong&gt;，没有数据拷贝，所以速度极快。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;list1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;begin&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; // 指向 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 将 list2 的所有元素 “移动” 到 list1 的 2 前面&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// list2 变空，list1 变为 {1, 4, 5, 6, 2, 3}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;list1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;splice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;list2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;专用成员函数&lt;a href=&quot;#专用成员函数&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;由于 &lt;code&gt;std::list&lt;/code&gt; 无法随机访问，标准库算法（如 &lt;code&gt;std::sort&lt;/code&gt;）对它无效。因此 &lt;code&gt;list&lt;/code&gt; 自带了一套经过优化的成员函数：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;l.sort()&lt;/code&gt;：排序（稳定排序，底层通常是归并排序）。注意：千万别读 &lt;code&gt;std::list&lt;/code&gt; 用 &lt;code&gt;std::sort(l.begin(), l.end())&lt;/code&gt;，编译会报错&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l.remove(val)&lt;/code&gt;：删除所有等于 &lt;code&gt;val&lt;/code&gt; 的元素&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l.remove_if(pred)&lt;/code&gt;：删除满足条件的元素&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l.unique()&lt;/code&gt;：删除&lt;strong&gt;相邻&lt;/strong&gt;的重复元素（去重前通常要先 sort）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l.reverse()&lt;/code&gt;：逆置链表&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l.merge(l2)&lt;/code&gt;：合并两个&lt;strong&gt;已排序&lt;/strong&gt;的链表（&lt;code&gt;l2&lt;/code&gt; 会变空）&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;迭代器特性&lt;a href=&quot;#迭代器特性&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这是决定选择 &lt;code&gt;std::vector&lt;/code&gt; 还是 &lt;code&gt;std::list&lt;/code&gt; 的关键因素。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;类型：&lt;strong&gt;双向迭代器&lt;/strong&gt;（&lt;em&gt;Bidirectional Iterator&lt;/em&gt;）
&lt;ul&gt;
&lt;li&gt;支持：&lt;code&gt;++it&lt;/code&gt;、&lt;code&gt;--it&lt;/code&gt;、&lt;code&gt;*it&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;不支持：&lt;code&gt;it + 5&lt;/code&gt;，&lt;code&gt;it &amp;lt; other_it&lt;/code&gt;（不能跳跃，不能比较大小）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;稳定性（Validity）：极高
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;掺入操作&lt;/strong&gt;：绝对不会让现有的迭代器失效&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删除操作&lt;/strong&gt;：只有指向&lt;strong&gt;被删除节点&lt;/strong&gt;的那个迭代器会失效，其他的完全不受影响&lt;/li&gt;
&lt;li&gt;对比 &lt;code&gt;std::vector&lt;/code&gt;：&lt;code&gt;std::vector&lt;/code&gt; 一旦扩容，所有迭代器全部失效；中间插入，后面所有迭代器失效。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;std::list&lt;/code&gt; 和 &lt;code&gt;std::vector&lt;/code&gt; 的选择&lt;a href=&quot;#stdlist-和-stdvector-的选择&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这是一个经典的 Trade-off（权衡）问题：&lt;/p&gt;







































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;特性&lt;/th&gt;&lt;th&gt;&lt;code&gt;std::vector&lt;/code&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;std::list&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;随机访问&lt;/td&gt;&lt;td&gt;&lt;span&gt;&lt;span&gt;O(1)O(1)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;（支持 &lt;code&gt;[]&lt;/code&gt;）&lt;/td&gt;&lt;td&gt;不支持（&lt;span&gt;&lt;span&gt;O(N)O(N)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;尾部插入&lt;/td&gt;&lt;td&gt;&lt;span&gt;&lt;span&gt;O(1)O(1)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span&gt;&lt;span&gt;O(1)O(1)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;头部/中间插入&lt;/td&gt;&lt;td&gt;&lt;span&gt;&lt;span&gt;O(N)O(N)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;（很慢，要挪动数据）&lt;/td&gt;&lt;td&gt;&lt;span&gt;&lt;span&gt;O(1)O(1)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;（很快，前提是有迭代器）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;内存布局&lt;/td&gt;&lt;td&gt;连续（Cache 命中率高）&lt;/td&gt;&lt;td&gt;分散（Cache 命中率低）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;额外内存&lt;/td&gt;&lt;td&gt;较少（少量预留空间）&lt;/td&gt;&lt;td&gt;较大（每个元素都要存两个指针）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;迭代器失效&lt;/td&gt;&lt;td&gt;容易失效&lt;/td&gt;&lt;td&gt;几乎不失效&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;结论：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;95% 的情况用 &lt;code&gt;std::vector&lt;/code&gt;：现代 CPU 的缓存机制非常依赖内存连续性。即使是在中间插入，对于小对象（如 &lt;code&gt;int&lt;/code&gt;），&lt;code&gt;std::vector&lt;/code&gt; 往往也比 &lt;code&gt;std::list&lt;/code&gt; 快，因为 &lt;code&gt;std::list&lt;/code&gt; 的遍历会导致大量的 Cache Miss。&lt;/li&gt;
&lt;li&gt;以下情况可以用 &lt;code&gt;std::list&lt;/code&gt;：
&lt;ol&gt;
&lt;li&gt;需要频繁在头部或中间插入 / 删除，且元素总数很多&lt;/li&gt;
&lt;li&gt;对象非常大，拷贝代价极高（虽然 C++11 移动语义缓解了这个问题）&lt;/li&gt;
&lt;li&gt;核心需求：你需要保存指向某个元素的迭代器，并且在后续操作中（无论怎么插入删除其他元素），希望这个迭代器一直有效&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;C++11 新增：&lt;code&gt;std::forword_list&lt;/code&gt;&lt;a href=&quot;#c11-新增stdforword_list&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;C++11 引入了 &lt;code&gt;std::forword_list&lt;/code&gt;（单向链表）：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;特点：只有 &lt;code&gt;next&lt;/code&gt; 指针，没有 &lt;code&gt;prev&lt;/code&gt; 指针&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优势&lt;/strong&gt;：比 &lt;code&gt;std::list&lt;/code&gt; 更省内存（少存一个指针）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;劣势&lt;/strong&gt;：只能向前遍历，功能受限&lt;/li&gt;
&lt;li&gt;场景：极其追求内存优化的哈希表桶实现等&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded></item><item><title>C++ 中的 vector</title><link>https://bfmhno3.github.io/posts/vector-in-cpp/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/vector-in-cpp/</guid><description>深入理解 C++ 中最常用的容器 std::vector：动态数组的核心特性、内存管理、扩容机制、迭代器失效问题，以及 reserve、emplace_back 等现代化最佳实践。特别提醒 std::vector&lt;bool&gt; 的陷阱和 C++20 新特性。</description><pubDate>Sat, 03 Jan 2026 10:50:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;std::vector&lt;/code&gt; 是 C++ 中&lt;strong&gt;最重要&lt;/strong&gt;、&lt;strong&gt;最常用&lt;/strong&gt;的容器，没有之一。它的本质是&lt;strong&gt;动态数组&lt;/strong&gt;（Dynamic Array）。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;std::vector&lt;/code&gt; 是在堆（Heap）上管理一块连续的内存，可以存放任意类型的对象。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;核心特性与底层原理&lt;a href=&quot;#核心特性与底层原理&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;头文件：&lt;code&gt;#include &amp;lt;vector&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;内存模型：&lt;strong&gt;连续内存&lt;/strong&gt;。这意味着它和 C 数组一样，支持通过指针偏移量快速访问，并且对 CPU 缓存（Cache）非常友好。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动扩容&lt;/strong&gt;：当存入数据量超过当前容量时，&lt;code&gt;std::vector&lt;/code&gt; 就会申请一块更大的内存（通常是原来的 1.5 倍或 2 倍），将旧数据移动/拷贝过去，然后释放旧内存。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;初始化与构造&lt;a href=&quot;#初始化与构造&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;vector&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 1. 默认构造（空 vector）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 2. 指定大小和默认值&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;v2&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt;        // 10 个元素，默认初始化 0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;v3&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt;     // 10 个元素，每个都是 5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 3. 列表初始化（C++11）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 4. 拷贝构造&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;v5&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;v4&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 5. 迭代器范围构造（常用与从其他容器拷贝）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;v6&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;容量与大小&lt;a href=&quot;#容量与大小&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;







































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;函数&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;th&gt;备注&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;size()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当前元素个数&lt;/td&gt;&lt;td&gt;实际存了多少个&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;capacity()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当前分配的内存能存多少个&lt;/td&gt;&lt;td&gt;&lt;code&gt;capacity&lt;/code&gt; &lt;span&gt;&lt;span&gt;⩾\geqslant&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;⩾&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;code&gt;size&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;empty()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;是否为空&lt;/td&gt;&lt;td&gt;推荐使用，比 &lt;code&gt;size() == 0&lt;/code&gt; 更语义化&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;reserve()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;预分配内存&lt;/td&gt;&lt;td&gt;仅改变 &lt;code&gt;capacity&lt;/code&gt;，不改变 &lt;code&gt;size&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;resize(n)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;改变元素个数&lt;/td&gt;&lt;td&gt;改变 &lt;code&gt;size&lt;/code&gt;，如果变大则填充默认值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;shrink_to_fit()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;释放未使用的内存（C++11）&lt;/td&gt;&lt;td&gt;让 &lt;code&gt;capacity&lt;/code&gt; 搜索到 &lt;code&gt;size&lt;/code&gt; 大小&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;section&gt;&lt;h3&gt;为什么 &lt;code&gt;reserve&lt;/code&gt; 非常重要？&lt;a href=&quot;#为什么-reserve-非常重要&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reserve&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; // 一次性分配好内存&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push_back&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; // 这里不会再发生内存重新分配，效率极高&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;增删查改&lt;a href=&quot;#增删查改&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;插入与添加&lt;a href=&quot;#插入与添加&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;push_back(val)&lt;/code&gt;：在尾部添加元素（会发生拷贝或移动）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;emplace_back(arg...)&lt;/code&gt;（C++11）：&lt;strong&gt;原地构造&lt;/strong&gt;。直接在 &lt;code&gt;std::vector&lt;/code&gt; 尾部构造对象，省去了一次临时对象的构造和拷贝 / 移动，&lt;strong&gt;效率通常更高&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;insert(it, val)&lt;/code&gt;：在迭代器指向的位置插入。效率为 &lt;span&gt;&lt;span&gt;O(N)O(N)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，因为要移动后续所有元素。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;删除&lt;a href=&quot;#删除&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pop_back&lt;/code&gt;：删除尾部元素（&lt;span&gt;&lt;span&gt;O(1)O(1)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;erase(it)&lt;/code&gt;：删除指定位置元素（&lt;span&gt;&lt;span&gt;O(N)O(N)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，后续元素前移）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clear()&lt;/code&gt;：清空所有元素，&lt;code&gt;szie&lt;/code&gt; 变为 0，但 &lt;code&gt;capacity&lt;/code&gt; 通常不变（内存不释放）。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;访问&lt;a href=&quot;#访问&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;v[i]&lt;/code&gt;：下标访问，不检查越界。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v.at[i]&lt;/code&gt;：检查越界，越觉抛 &lt;code&gt;std::out_of_range&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v.front()&lt;/code&gt; / &lt;code&gt;v.back()&lt;/code&gt;：访问首尾。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v.data()&lt;/code&gt;：返回指向底层数组首元素的指针（&lt;code&gt;T*&lt;/code&gt;）。常用于和 C 语言 API 交互。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;迭代器失效&lt;a href=&quot;#迭代器失效&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;由于 &lt;code&gt;std::vector&lt;/code&gt; 是连续内存，当结构发生变化时，指向旧内存的&lt;strong&gt;迭代器&lt;/strong&gt;、&lt;strong&gt;指针&lt;/strong&gt;、&lt;strong&gt;引用&lt;/strong&gt;可能会失效。&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;扩容时失效：当 &lt;code&gt;push_back&lt;/code&gt; 导致 &lt;code&gt;std::vector&lt;/code&gt; 扩容（&lt;code&gt;reallocate&lt;/code&gt;）时，原内存被释放，&lt;strong&gt;所有&lt;/strong&gt;指向原数据的迭代器 / 指针瞬间全部失效。&lt;/li&gt;
&lt;li&gt;插入 / 删除时失效：当 &lt;code&gt;insert&lt;/code&gt; 或 &lt;code&gt;erase&lt;/code&gt; 一个位置时，该位置&lt;strong&gt;之后&lt;/strong&gt;的所有迭代器都会失效（因为数据移动了）。&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;begin&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;erase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; // 错误！erase 后 it 已失效，下一次 ++it 会崩溃&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 正确写法（利用 erase 返回值更新迭代器）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;begin&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;erase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; // erase 返回指向下一个元素的迭代器&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;特殊版本：&lt;code&gt;std::vector&amp;lt;bool&amp;gt;&lt;/code&gt;&lt;a href=&quot;#特殊版本stdvectorbool&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这是一个历史遗留的 “坑”。为了节省空间，C++ 标准库特化了 &lt;code&gt;std::vector&amp;lt;bool&amp;gt;&lt;/code&gt;，它不是存储 &lt;code&gt;bool&lt;/code&gt;（1 字节），而是存储 &lt;code&gt;bit&lt;/code&gt;（1 比特）。&lt;/p&gt;&lt;p&gt;后果：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;你无法获得元素的地址：&lt;code&gt;&amp;amp;v[0]&lt;/code&gt; 是非法的，因为无法寻址单个比特。&lt;/li&gt;
&lt;li&gt;它的 &lt;code&gt;operator[]&lt;/code&gt; 返回的不是 &lt;code&gt;bool&amp;amp;&lt;/code&gt;，而是一个代理对象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非线程安全&lt;/strong&gt;：并发读写邻近的 bit 可能会导致数据竞争（因为它们位于同一个字节内）。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;建议：如果需要存布尔值且不缺那点内存，用 &lt;code&gt;std::vector&amp;lt;char&amp;gt;&lt;/code&gt; 或 &lt;code&gt;std::deque&amp;lt;bool&amp;gt;&lt;/code&gt; 代替。如果确实需要位操作，考虑使用 &lt;code&gt;std::bitset&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;现代化操作&lt;a href=&quot;#现代化操作&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;C++20：&lt;code&gt;std::erase&lt;/code&gt; 和 &lt;code&gt;std::erase_if&lt;/code&gt;&lt;a href=&quot;#c20stderase-和-stderase_if&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在 C++20 之前，要从 &lt;code&gt;std::vector&lt;/code&gt; 中删除满足特定条件的所以元素，需要使用 “Erase-Remove Idiom”（&lt;code&gt;v.erase(std::remove(...), v.end()）&lt;/code&gt;，非常啰嗦。&lt;/p&gt;&lt;p&gt;C++20 简化了：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;vector&lt;/span&gt;&lt;span&gt;&amp;lt;int&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 删除所有偶数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;std&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span&gt;erase_if&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;, [](&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最佳实践&lt;a href=&quot;#最佳实践&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;优先使用 &lt;code&gt;emplace_back&lt;/code&gt;：代替 &lt;code&gt;push_back&lt;/code&gt;，特别是存放复杂对象时。&lt;/li&gt;
&lt;li&gt;善用 &lt;code&gt;reserve&lt;/code&gt;：如果你能预估数据量，一定要先 &lt;code&gt;reserve&lt;/code&gt;，在数据量较大时，能够极大的优化性能。&lt;/li&gt;
&lt;li&gt;避免头部/中间插入：在 &lt;code&gt;std::vector&lt;/code&gt; 头部插入数据（&lt;code&gt;insert(begin(), val)&lt;/code&gt;）是非常慢的（&lt;span&gt;&lt;span&gt;O(N)O(N)&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;O&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;），如果有这种需求，请改用 &lt;code&gt;std::deque&lt;/code&gt; 或 &lt;code&gt;std::list&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;慎用 &lt;code&gt;std::vector&amp;lt;bool&amp;gt;&lt;/code&gt;：除非你清楚你自己在做什么。&lt;/li&gt;
&lt;li&gt;小心引用失效：在循环中做 &lt;code&gt;push_back&lt;/code&gt; 时，千万不要同时持有指向该 &lt;code&gt;std::vector&lt;/code&gt; 内部元素的引用，一旦扩容，引用就变成悬空指针了。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;</content:encoded></item><item><title>C++ 中的 string</title><link>https://bfmhno3.github.io/posts/string-in-cpp/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/string-in-cpp/</guid><description>深入理解 C++ 标准库中的 std::string 类：从基本用法到底层原理，涵盖初始化、容量管理、元素访问、修改操作、类型转换等核心知识点，以及 SSO 优化、string_view 等现代特性和最佳实践。</description><pubDate>Sat, 03 Jan 2026 08:28:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;std::string&lt;/code&gt; 是 C++ Standard Library 提供的标准类之一，这也是 C++ 开发中最常用的类之一，它本质上是对 C 风格字符串（&lt;code&gt;char*&lt;/code&gt;）的封装，提供了自动内存管理和丰富的操作接口。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;核心概念&lt;a href=&quot;#核心概念&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;std::string&lt;/code&gt; 位于 &lt;code&gt;&amp;lt;string&amp;gt;&lt;/code&gt; 头文件中，本质上是对 &lt;code&gt;std::basic_string&amp;lt;char&amp;gt;&lt;/code&gt; 的特化版本。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;头文件：&lt;code&gt;#include &amp;lt;string&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;命名空间：&lt;code&gt;std&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;特点：动态管理内存，自动处理扩容，兼容 C 风格字符串。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;初始化与构造&lt;a href=&quot;#初始化与构造&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;string&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;vector&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;                     // 默认构造，空字符串 &quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Hello, World&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;    // 拷贝初始化&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s3&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Hello, World&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt;     // 直接初始化&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s4&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;A&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt;             // 生成 &quot;AAAAA&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s5&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt;                 // 拷贝构造&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s6&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt;           // 子串构造：从索引 1 开始取 3 个字符 -&amp;gt; &quot;ell&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// C++11 及以后&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s7&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;&apos;H&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;i&apos;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;span&gt;        // 列表初始化&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;容量与属性&lt;a href=&quot;#容量与属性&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;了解容量对于优化性能至关重要，特别是避免不必要的内存重分配。&lt;/p&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;函数&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;th&gt;备注&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;size()&lt;/code&gt; / &lt;code&gt;length()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;返回字符数量&lt;/td&gt;&lt;td&gt;两者完全等价，一般用 &lt;code&gt;size()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;empty()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;判断是否为空&lt;/td&gt;&lt;td&gt;推荐使用，比 &lt;code&gt;size() == 0&lt;/code&gt; 语义更清晰&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;capacity()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当前分配的内存容量&lt;/td&gt;&lt;td&gt;通常 &lt;code&gt;&amp;gt;=size()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;reserve(n)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;预分配 &lt;code&gt;n&lt;/code&gt; 字节内存&lt;/td&gt;&lt;td&gt;避免 &lt;code&gt;append&lt;/code&gt; 时的多次内存重分配&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;shrink_to_fit()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;释放多余内存（C++11）&lt;/td&gt;&lt;td&gt;让 &lt;code&gt;capacity&lt;/code&gt; 接近 &lt;code&gt;size&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;元素访问与遍历&lt;a href=&quot;#元素访问与遍历&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;访问单个字符&lt;a href=&quot;#访问单个字符&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;operator[]&lt;/code&gt;：&lt;code&gt;s[i]&lt;/code&gt;。&lt;strong&gt;不检查越界&lt;/strong&gt;，效率高，但越界为导致未定义行为。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;at()&lt;/code&gt;：&lt;code&gt;s.at[i]&lt;/code&gt;。&lt;strong&gt;检查越界&lt;/strong&gt;，越界会抛出 &lt;code&gt;std::out_of_range&lt;/code&gt; 异常。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;front()&lt;/code&gt; / &lt;code&gt;back()&lt;/code&gt;：访问首尾字符。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;C 风格字符串&lt;a href=&quot;#c-风格字符串&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;c_str()&lt;/code&gt;：返回 &lt;code&gt;const char*&lt;/code&gt;，以 &lt;code&gt;NULL&lt;/code&gt; 结尾。主要用于兼容旧式 C 接口（如 &lt;code&gt;printf&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data()&lt;/code&gt;：C++11 前不保证 &lt;code&gt;NULL&lt;/code&gt; 结尾，C++11 后与 &lt;code&gt;c_str()&lt;/code&gt; 基本一致。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;遍历&lt;a href=&quot;#遍历&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Hello, World&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 1. 基于下标&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;size_t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;span&gt; /* ... */&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 2. 基于迭代器&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;begin&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;span&gt; /* ... */&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 3. 基于范围 for 循环（C++11，推荐）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;span&gt; /* ... */&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;       // 值拷贝&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;char&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;toupper&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) }&lt;/span&gt;&lt;span&gt; // 引用修改&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;修改与操作&lt;a href=&quot;#修改与操作&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;







































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;操作&lt;/th&gt;&lt;th&gt;函数&lt;/th&gt;&lt;th&gt;示例&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;追加&lt;/td&gt;&lt;td&gt;&lt;code&gt;+=&lt;/code&gt;、&lt;code&gt;append()&lt;/code&gt;、&lt;code&gt;push_back()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;s += &quot; World&quot;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;插入&lt;/td&gt;&lt;td&gt;&lt;code&gt;insert(pos, str)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;s.insert(0, &quot;Hi &quot;)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;删除&lt;/td&gt;&lt;td&gt;&lt;code&gt;erase(pos, len), pop_back()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;s.erase(0, 3)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;替换&lt;/td&gt;&lt;td&gt;&lt;code&gt;replace(pos, len, str)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;s.replace(6, 5, &quot;C++&quot;)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;清空&lt;/td&gt;&lt;td&gt;&lt;code&gt;clear()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;把 &lt;code&gt;size&lt;/code&gt; 设置为 0，&lt;code&gt;capacity&lt;/code&gt; 通常不变&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;截断/扩充&lt;/td&gt;&lt;td&gt;&lt;code&gt;resize(n)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;改变 &lt;code&gt;size&lt;/code&gt;，多出的补默认值&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;查找与子串&lt;a href=&quot;#查找与子串&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;所有查找函数如果未找到，都会返回常量 &lt;code&gt;std::string::npos&lt;/code&gt;（通常是 &lt;code&gt;-1&lt;/code&gt; 的无符号形式，即最大的 &lt;code&gt;size_t()&lt;/code&gt;）。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;filename.txt&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 1. 查找 find&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;size_t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pos&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;pos&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; std::string::&lt;/span&gt;&lt;span&gt;npos&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 找到了&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 2. 反向查找 rfind（找最后一个出现的位置）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;size_t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;last_dot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rfind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 3. 查找集合 find_first_of / find_first_not_of&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 查找第一个出现的元音字母&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;size_t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v_pos&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;find_first_of&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;aeiou&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 4. 获取子串 substr&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 参数：substr(开始位置, 长度)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;substr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;pos&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; // &quot;filename&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;substr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pos&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; // &quot;txt&quot;（省略长度则取到末尾）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;类型转换&lt;a href=&quot;#类型转换&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;需要包含头文件 &lt;code&gt;&amp;lt;string&amp;gt;&lt;/code&gt;。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;数值转字符串 &lt;code&gt;std::to_string(value)&lt;/code&gt;&lt;a href=&quot;#数值转字符串-stdto_stringvalue&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;std::&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; std::&lt;/span&gt;&lt;span&gt;to_string&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3.14&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;span&gt; // &quot;3.140000&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;字符串转数值&lt;a href=&quot;#字符串转数值&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;std::stoi(s)&lt;/code&gt;：&lt;code&gt;int&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;std::stol(s)&lt;/code&gt;：&lt;code&gt;long&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;std::stod(s)&lt;/code&gt;：&lt;code&gt;double&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;注意：这些函数会处理前导空格，但遇到非法字符会抛异常&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;现代化特性&lt;a href=&quot;#现代化特性&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;C++17：&lt;code&gt;std::string_view&lt;/code&gt;&lt;a href=&quot;#c17stdstring_view&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;std::string&lt;/code&gt; 是 “拥有者”，其拷贝开销大。&lt;code&gt;std::string_view&lt;/code&gt; 只是一个 “观察者”（包含指针和长度），零拷贝。&lt;/p&gt;&lt;p&gt;函数参数尽量使用 &lt;code&gt;std::string_view&lt;/code&gt; 代替 &lt;code&gt;const std::string&amp;amp;&lt;/code&gt;，特别是当你只需要读取字符串内容时。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 避免了临时 std::string 对象的构造和内存分配&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;print_prefix&lt;/span&gt;&lt;span&gt;(std::&lt;/span&gt;&lt;span&gt;string_view&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sv&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sv&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;substr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Pre&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;span&gt; // substr 也是 O(1) 操作&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;C++20&lt;a href=&quot;#c20&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;填补了长期以来 C++ &lt;code&gt;std::string&lt;/code&gt; 缺少高频接口的遗憾：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;s.starts_with(&quot;prefix&quot;)&lt;/code&gt;：判断前缀&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s.ends_with(&quot;suffix&quot;)&lt;/code&gt;：判断后缀&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s.contains(&quot;sub&quot;)&lt;/code&gt;：判断包含&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;底层原理&lt;a href=&quot;#底层原理&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;SSO（Small String Optimization）&lt;a href=&quot;#ssosmall-string-optimization&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;原理&lt;/strong&gt;：为了避免短字符串频繁的堆内存分配（Heap Allocation），&lt;code&gt;std::string&lt;/code&gt; 内部通常有一个小的栈缓冲区（通常 15 或 22 字节，取决于编译器实现）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;效果&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;如果字符串很短（例如：&lt;code&gt;&quot;Hello&quot;&lt;/code&gt;），直接存放在栈上对象内部，无 &lt;code&gt;malloc&lt;/code&gt;，速度极快。&lt;/li&gt;
&lt;li&gt;如果字符串变长，才会在堆上分配内存，指针指向堆。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;COW（Copy-On-Write）&lt;a href=&quot;#cowcopy-on-write&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在 C++11 之后已被废弃。现在的标准规定 &lt;code&gt;std::string&lt;/code&gt; 不允许使用 COW。这意味着 &lt;code&gt;std::string a = b;&lt;/code&gt; 一定会发生深拷贝（除非使用 &lt;code&gt;std::move&lt;/code&gt;）。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最佳实践&lt;a href=&quot;#最佳实践&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;参数传递&lt;a href=&quot;#参数传递&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;只读不拥有：C++17 前用 &lt;code&gt;const std::string&amp;amp;&lt;/code&gt;，C++17 后优先用 &lt;code&gt;std::string_view&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;需要修改：用 &lt;code&gt;std::string&amp;amp;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;需要拥有权（Sink）：可以直接传值 &lt;code&gt;std::string&lt;/code&gt; 并配合 &lt;code&gt;std::move&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;性能优化&lt;a href=&quot;#性能优化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;在循环中追加字符串前，先用 &lt;code&gt;reserve()&lt;/code&gt; 预留空间（写在循环之前）。&lt;/li&gt;
&lt;li&gt;尽量避免在循环内部频繁构造 &lt;code&gt;std::string&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;换行符优先用 &lt;code&gt;\n&lt;/code&gt; 而不是 &lt;code&gt;std::endl&lt;/code&gt;（除非你需要立即刷新缓冲区）。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;安全性&lt;a href=&quot;#安全性&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;永远不要对 &lt;code&gt;c_str()&lt;/code&gt; 返回的指针进行 &lt;code&gt;delete&lt;/code&gt; 操作。&lt;/li&gt;
&lt;li&gt;当 &lt;code&gt;string&lt;/code&gt; 对象被修改或销毁后，之前获取的 iterators、references 和 pointers（如 &lt;code&gt;c_str()&lt;/code&gt; 的结果）可能会失效。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>Windows 包管理神器 Scoop：从入门到自定义安装配置</title><link>https://bfmhno3.github.io/posts/windows-scoop-package-manager-guide/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/windows-scoop-package-manager-guide/</guid><description>Windows 下最好用的命令行包管理工具 Scoop 保姆级教程。涵盖自定义安装路径、Aria2 加速配置、常用软件推荐以及常见问题解决。</description><pubDate>Fri, 05 Dec 2025 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;如果你习惯了 Linux 下 &lt;code&gt;apt&lt;/code&gt;、&lt;code&gt;pacman&lt;/code&gt; 或 macOS 下 &lt;code&gt;brew&lt;/code&gt; 的爽快体验，那么回到 Windows 面对满屏的安装包和 “下一步” 肯定会感到繁琐。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scoop&lt;/strong&gt; 是 Windows 下的一款轻量级包管理工具。与 Chocolatey 或 Winget 不同，Scoop 的设计哲学是 &lt;strong&gt;“非侵入式”&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;权限洁癖&lt;/strong&gt;：默认安装在用户目录，无需 UAC 提权（除非安装全局软件）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;绿色环保&lt;/strong&gt;：自动处理环境变量（Shim 机制），卸载时瞬间清除，不残留注册表垃圾。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;版本控制&lt;/strong&gt;：利用 Git 管理软件仓库（Bucket），方便版本回退。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文将带你从零开始配置一个完美的 Scoop 环境。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;1. 调整 Scoop 安装位置&lt;a href=&quot;#1-调整-scoop-安装位置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Scoop 默认将软件安装在 C 盘。作为开发者，我们通常希望将工具链与系统盘分离。在运行安装脚本前，我们需要先设置环境变量。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;还没有安装 Scoop（推荐）&lt;a href=&quot;#还没有安装-scoop推荐&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;打开 &lt;strong&gt;PowerShell&lt;/strong&gt;（无需管理员权限），依次执行以下命令。假设我们要安装到 &lt;code&gt;D:\Users\abc\scoop&lt;/code&gt;（请根据你的实际情况修改路径）。&lt;/p&gt;&lt;section&gt;&lt;h4&gt;第一步：设置用户安装目录&lt;a href=&quot;#第一步设置用户安装目录&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;这是普通软件（Chrome, VSCode 等）的安装位置。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$env:SCOOP&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;D:\Users\abc\scoop&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Environment&lt;/span&gt;&lt;span&gt;]::SetEnvironmentVariable(&lt;/span&gt;&lt;span&gt;&apos;SCOOP&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;$env&lt;/span&gt;&lt;span&gt;::SCOOP, &lt;/span&gt;&lt;span&gt;&apos;User&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;第二步：设置全局安装目录（可选）&lt;a href=&quot;#第二步设置全局安装目录可选&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;这是需要管理员权限（如驱动、系统级工具）的软件安装位置。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$env:SCOOP_GLOBAL&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;D:\ProgramData\scoop&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Environment&lt;/span&gt;&lt;span&gt;]::SetEnvironmentVariable(&lt;/span&gt;&lt;span&gt;&apos;SCOOP_GLOBAL&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;$env&lt;/span&gt;&lt;span&gt;::SCOOP_GLOBAL, &lt;/span&gt;&lt;span&gt;&apos;Machine&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Note&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：修改 &lt;code&gt;Machine&lt;/code&gt; 级别的变量通常需要&lt;strong&gt;管理员权限&lt;/strong&gt;的 PowerShell。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;警告：路径规范&lt;/strong&gt;
Scoop 的安装路径中&lt;strong&gt;严禁包含空格和中文字符&lt;/strong&gt;！&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;❌ 错误示例：&lt;code&gt;D:\Program Files\Scoop&lt;/code&gt; 或 &lt;code&gt;D:\我的软件\Scoop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;✅ 正确示例：&lt;code&gt;D:\Users\abc\scoop&lt;/code&gt; 或 &lt;code&gt;D:\Scoop&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;备选方案：通过图形界面设置&lt;a href=&quot;#备选方案通过图形界面设置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;如果你不习惯命令行，也可以通过 Windows 设置：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;搜索 &lt;strong&gt;“编辑系统环境变量”&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;“环境变量(N)…”&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;在 &lt;strong&gt;“用户变量”&lt;/strong&gt; 中新建 &lt;code&gt;SCOOP&lt;/code&gt;，值为你的自定义路径。&lt;/li&gt;
&lt;li&gt;在 &lt;strong&gt;“系统变量”&lt;/strong&gt; 中新建 &lt;code&gt;SCOOP_GLOBAL&lt;/code&gt;，值为你的全局路径。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;已经安装了 Scoop（迁移）&lt;a href=&quot;#已经安装了-scoop迁移&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;如果你已经安装了 Scoop 但想迁移目录，操作稍微繁琐一些，因为 Scoop 不写注册表，它是“绿色”的。&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;将原目录（默认 &lt;code&gt;C:\Users\&amp;lt;user_name&amp;gt;\scoop&lt;/code&gt;）&lt;strong&gt;剪切&lt;/strong&gt;并&lt;strong&gt;粘贴&lt;/strong&gt;到新目录（例如 &lt;code&gt;D:\Scoop&lt;/code&gt;）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改系统环境变量 &lt;code&gt;Path&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;删除指向旧目录的条目（如 &lt;code&gt;C:\Users\...\scoop\shims&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;添加新目录的 shim 路径（如 &lt;code&gt;D:\Scoop\shims&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;关键步骤&lt;/strong&gt;：打开新的 PowerShell 终端，执行以下命令重置所有软件的链接：&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop reset &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2. 安装 Scoop&lt;a href=&quot;#2-安装-scoop&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;环境变量配置完成后，在 PowerShell 中执行以下命令进行安装：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Set-ExecutionPolicy&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;ExecutionPolicy RemoteSigned &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;Scope CurrentUser&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Invoke-RestMethod&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;Uri [&lt;/span&gt;&lt;/span&gt;&lt;span&gt;https&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;/span&gt;&lt;span&gt;get.scoop.sh&lt;/span&gt;&lt;span&gt;&lt;span&gt;](https:&lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt;get.scoop.sh) | &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Invoke-Expression&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;第一行：允许当前用户运行本地脚本。&lt;/li&gt;
&lt;li&gt;第二行：下载并执行 Scoop 官方安装脚本。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;安装完成后，输入 &lt;code&gt;scoop help&lt;/code&gt;，如果有输出，说明安装成功。&lt;/p&gt;&lt;p&gt;如果在新开启一个终端后执行 &lt;code&gt;scoop&lt;/code&gt; 报错，则需要配置系统 &lt;code&gt;PATH&lt;/code&gt; 变量，添加例如：&lt;code&gt;D:\Users\abc\scoop\shims&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;3. 初始化配置&lt;a href=&quot;#3-初始化配置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;安装必备组件&lt;a href=&quot;#安装必备组件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Scoop 依赖 Git 来管理仓库，同时强烈建议安装 Aria2 来进行多线程下载加速。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;scoop install git aria2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;安装完成后，建议对 Aria2 进行调优，关闭警告并开启多线程：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop config aria2&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;warning&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;enabled false&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop config aria2&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;retry&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;wait &lt;/span&gt;&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop config aria2&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;connection&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;per&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;server &lt;/span&gt;&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop config aria2&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;split &lt;/span&gt;&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop config aria2&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;split-size&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1M&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;添加常用软件仓库 (Buckets)&lt;a href=&quot;#添加常用软件仓库-buckets&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Scoop 默认的 &lt;code&gt;main&lt;/code&gt; 仓库软件较少且由于标准严格，通常只包含 CLI 工具。我们需要添加社区仓库来扩展生态。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;scoop bucket add extras      &lt;/span&gt;&lt;span&gt;# 包含大量 GUI 软件 (Chrome, VSCode 等)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;scoop bucket add versions    &lt;/span&gt;&lt;span&gt;# 包含旧版本软件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;scoop bucket add java        &lt;/span&gt;&lt;span&gt;# Java 开发相关&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop bucket add nerd&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;fonts  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# 编程字体 (FiraCode NF, JetBrainsMono NF)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;4. 装机必备软件清单&lt;a href=&quot;#4-装机必备软件清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这里列出了一份高频使用的软件清单，涵盖了开发、系统增强和效率工具。&lt;/p&gt;&lt;p&gt;你可以一次性复制执行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 系统工具与命令行增强&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop install gsudo everything scoop&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;search starship fzf ripgrep bat powertoys everything&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;powertoys&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 开发工具&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop install uv neovim vscode jetbrains&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;toolbox&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;软件清单详解：&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gsudo&lt;/code&gt;: Windows 下的 &lt;code&gt;sudo&lt;/code&gt;，比官方的更强，体验与 Linux 一致。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scoop-search&lt;/code&gt;: 秒级搜索 Scoop 仓库软件（比原生 &lt;code&gt;scoop search&lt;/code&gt; 快几十倍）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;everything&lt;/code&gt;: Windows 文件搜索神器。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;powertoys&lt;/code&gt;: 微软官方出品的系统增强工具集。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;everything-powertoys&lt;/code&gt;: 让 PowerToys 的启动器调用 Everything 的索引。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;starship&lt;/code&gt;: 极速、高颜值的终端提示符（Prompt），支持显示 Git 状态、Python 版本等。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fzf&lt;/code&gt;: 命令行模糊查找工具。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ripgrep&lt;/code&gt; (&lt;code&gt;rg&lt;/code&gt;): 速度极快的 &lt;code&gt;grep&lt;/code&gt; 替代品。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bat&lt;/code&gt;: 带语法高亮和 Git 集成的 &lt;code&gt;cat&lt;/code&gt; 替代品。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv&lt;/code&gt;: Rust 编写的 Python 包版本管理器，速度飞快。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;neovim&lt;/code&gt;: 现代化 Vim 编辑器。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vscode&lt;/code&gt;: 最流行的代码编辑器。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jetbrains-toolbox&lt;/code&gt;: 管理 IDEA, PyCharm 等 JetBrains 全家桶。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;5. 日常维护与进阶&lt;a href=&quot;#5-日常维护与进阶&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;更新软件&lt;a href=&quot;#更新软件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Scoop 的更新分为两步：先更新仓库列表，再更新软件。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;scoop update           &lt;/span&gt;&lt;span&gt;# 更新 scoop 本身和 bucket&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop update &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;         &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# 更新所有已安装的软件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;清理旧版本&lt;a href=&quot;#清理旧版本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Scoop 默认会保留软件的旧版本（为了方便回退）。如果你的磁盘空间紧张，可以清理旧包：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop cleanup &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# 清理所有软件的旧版本&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;软件备份与迁移&lt;a href=&quot;#软件备份与迁移&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是我最喜欢 Scoop 的一点：你可以导出一份列表，在另一台电脑上瞬间还原你的工作环境。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;导出列表：&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop export &amp;gt; scoop&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;apps.json&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;在另一台电脑恢复：&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop import scoop&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;apps.json&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;6. 常见问题解决&lt;a href=&quot;#6-常见问题解决&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;Windows 搜索找不到安装的软件&lt;a href=&quot;#windows-搜索找不到安装的软件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：安装了 Chrome 或 VS Code，但按 Win 键搜索不到。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：Windows 索引默认只覆盖特定的开始菜单目录，具体路径为：&lt;code&gt;C:\Users\&amp;lt;user&amp;gt;\AppData\Roaming\Microsoft\Windows\Start Menu\Programs&lt;/code&gt;（在运行窗口中输入 &lt;code&gt;shell:programs&lt;/code&gt; 后点击 &lt;kbd&gt;Enter&lt;/kbd&gt; 也可直接打开）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;解决方法&lt;/strong&gt;：
Scoop 实际上会自动创建快捷方式到 &lt;code&gt;C:\Users\&amp;lt;user&amp;gt;\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Scoop Apps&lt;/code&gt;。如果搜索不到，通常是因为索引未更新或快捷方式未正确生成。&lt;/p&gt;&lt;p&gt;尝试执行以下命令重置：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 重置指定软件的快捷方式&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop reset &amp;lt;package&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;name&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 或者重置所有软件（推荐）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;scoop reset &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;reset&lt;/code&gt; 命令会重新执行安装脚本中的 “链接” 步骤，重建 &lt;code&gt;.exe&lt;/code&gt; 的 shim 和开始菜单快捷方式。&lt;/p&gt;&lt;p&gt;此外，也可以使用 Scoop 自带的体检工具来发现路径或权限问题：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;scoop checkup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>英语翻译技巧</title><link>https://bfmhno3.github.io/posts/english-translation-techniques/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/english-translation-techniques/</guid><description>本文深入解析了中英翻译中的核心思维差异（如动态与静态、意合与形合），介绍了英语句法的平衡学原则（尾重原则），并提供了一套实用的“翻译三步法”（拆解、降级、润色），旨在帮助备考 CET-6 或雅思的同学克服中式英语，提升学术写作与翻译水平。</description><pubDate>Tue, 02 Dec 2025 01:35:00 GMT</pubDate><content:encoded>&lt;p&gt;在备考 CET-6 或雅思写作时，许多同学面临的最大障碍不是词汇量，而是&lt;strong&gt;思维模式（Mindset）&lt;/strong&gt;。中英文在底层逻辑上存在巨大的鸿沟：中文重意合、动态、叙事；英文重形合、静态、结构。&lt;/p&gt;
&lt;p&gt;本文将拆解中英翻译的&lt;strong&gt;核心差异&lt;/strong&gt;，并提供一套可操作的&lt;strong&gt;翻译三步法&lt;/strong&gt;。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;一、 核心思维差异（Core Divergences）&lt;a href=&quot;#一-核心思维差异core-divergences&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;1. 动态 vs. 静态（Nominalization）&lt;a href=&quot;#1-动态-vs-静态nominalization&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;法则：&lt;/strong&gt; 汉语多用&lt;strong&gt;动词&lt;/strong&gt;（Verb-heavy）；学术英语多用&lt;strong&gt;名词&lt;/strong&gt;（Noun-heavy）。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;在翻译时，要学会&lt;strong&gt;名词化（Nominalization）&lt;/strong&gt;：将中文的动作或性质抽象为英语的名词短语。这能显著提升句子的“信息密度”。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原文&lt;/strong&gt;：这就解释了为什么我们需要从不同角度看问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;❌ Chinglish:&lt;/strong&gt; This explains why we need to &lt;strong&gt;see&lt;/strong&gt; the problem from different angles. (动词堆砌)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;✅ Academic:&lt;/strong&gt; This is an &lt;strong&gt;illustration&lt;/strong&gt; of the &lt;strong&gt;need&lt;/strong&gt; for a different &lt;strong&gt;perspective&lt;/strong&gt; on the issue. (名词主导)&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. 主动 vs. 被动（Objectivity）&lt;a href=&quot;#2-主动-vs-被动objectivity&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;法则：&lt;/strong&gt; 汉语习惯“人”做主语；英语习惯“物”做主语，强调客观事实。&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原文:&lt;/strong&gt; 人们普遍认为，阅读能扩大词汇量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;❌ Chinglish:&lt;/strong&gt; People generally believe that reading can increase vocabulary. (语法正确，但太口语)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;✅ Academic:&lt;/strong&gt; It is widely &lt;strong&gt;acknowledged&lt;/strong&gt; that vocabulary can be &lt;strong&gt;expanded&lt;/strong&gt; through reading. (被动结构，客观严谨)&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3. 意合 vs. 形合（Explicit Logic）&lt;a href=&quot;#3-意合-vs-形合explicit-logic&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;法则：&lt;/strong&gt; 汉语逻辑往往隐含在语序中（隐性）；英语逻辑必须通过&lt;strong&gt;连接词&lt;/strong&gt;显性表达（显性）。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;中文常用“流水句”，英语则必须用逻辑词像胶水一样把句子粘起来。&lt;/p&gt;
























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;逻辑关系&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;中文结构 (隐性)&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;英文连接词 (显性)&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;因果&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;天气冷，电池坏了。&lt;/td&gt;&lt;td&gt;The battery failed &lt;strong&gt;due to&lt;/strong&gt; the low temperature.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;转折&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;代码跑通了，效率很低。&lt;/td&gt;&lt;td&gt;The code runs successfully; &lt;strong&gt;however&lt;/strong&gt;, its efficiency is low.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;递进&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;他修好了bug，优化了算法。&lt;/td&gt;&lt;td&gt;He fixed the bug; &lt;strong&gt;furthermore&lt;/strong&gt;, he optimized the algorithm.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;二、 句子的平衡学（Syntactic Balance）&lt;a href=&quot;#二-句子的平衡学syntactic-balance&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;1. 尾重原则（End-Weight）&lt;a href=&quot;#1-尾重原则end-weight&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;英语句法中，句子是有“物理重量”的。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;轻元素：&lt;/strong&gt; 代词、系动词 (is/are)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重元素：&lt;/strong&gt; 长从句、复杂修饰语。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;为了避免“头重脚轻”，英语倾向于&lt;strong&gt;快速给出主谓骨架&lt;/strong&gt;，把复杂的修饰扔到后面。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原文:&lt;/strong&gt; 掌握一门外语对大学生来说是至关重要的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;❌ Chinglish:&lt;/strong&gt; &lt;em&gt;To master a foreign language&lt;/em&gt; is crucial for college students. (主语太长)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;✅ Academic:&lt;/strong&gt; &lt;strong&gt;It is&lt;/strong&gt; crucial for college students &lt;strong&gt;to master a foreign language proficiently&lt;/strong&gt;. (形式主语 It 占位)&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;尾重原则&lt;/strong&gt;出现是因为&lt;strong&gt;人类的短期记忆是有限的&lt;/strong&gt;。如果主语（Subject）太长，听者/读者要在大脑里面一直“悬着”这个主语，等很久才能等到谓语（Verb），这会造成认知负担（Cognitive Load）。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. 如何选择主句（Subject Selection）&lt;a href=&quot;#2-如何选择主句subject-selection&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;英语句子只能有一个核心（Main Clause），其他信息必须降级。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;结果（观点） &amp;gt; 原因/方式：&lt;/strong&gt; 英语看重 Result（新信息）。
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;原文:&lt;/em&gt; 他每天努力学习，终于考上了北大。&lt;/li&gt;
&lt;li&gt;&lt;em&gt;译文:&lt;/em&gt; He was admitted to Peking University (&lt;strong&gt;主句&lt;/strong&gt;) by studying hard every day.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实意动作 &amp;gt; 状态描述：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;原文:&lt;/em&gt; 他是个热心人，经常帮助邻居。&lt;/li&gt;
&lt;li&gt;&lt;em&gt;译文:&lt;/em&gt; Being a warm-hearted man (&lt;strong&gt;降级为分词&lt;/strong&gt;), he frequently helps his neighbors (&lt;strong&gt;主句&lt;/strong&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三、 翻译三步法&lt;a href=&quot;#三-翻译三步法&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;Step 1: 拆解与定位 (Analysis)&lt;a href=&quot;#step-1-拆解与定位-analysis&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;不要一上来就翻。先找到中文里“谁”做了“什么”。如果句子过长（比如一段话只有一个句号的场景），需要根据句子表达的含义对句子进行划分层次，对每个层次分别进行翻译，再使用逻辑词联系上下文。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;注意：&lt;/strong&gt; 中文常省略主语，翻译时必须补全（如补 &lt;code&gt;It&lt;/code&gt; 或 &lt;code&gt;There be&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Step 2: 转换与降级 (Syntactic Demotion)&lt;a href=&quot;#step-2-转换与降级-syntactic-demotion&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;确定主句后，对其余成分进行&lt;strong&gt;语法降级&lt;/strong&gt;：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;V-ing / V-ed (分词):&lt;/strong&gt; 表伴随、原因。 (&lt;em&gt;Standing at the window…&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preposition (介词):&lt;/strong&gt; 学术神器 &lt;code&gt;With&lt;/code&gt;, &lt;code&gt;By&lt;/code&gt;, &lt;code&gt;Due to&lt;/code&gt;, &lt;code&gt;In terms of&lt;/code&gt;. (&lt;em&gt;With the population increasing…&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clause (从句):&lt;/strong&gt; &lt;code&gt;Which&lt;/code&gt;, &lt;code&gt;That&lt;/code&gt;, &lt;code&gt;Although&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Step 3: 润色与精准 (Polishing)&lt;a href=&quot;#step-3-润色与精准-polishing&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;拒绝绝对化 (Hedging):&lt;/strong&gt; 除非是定理，否则少用 &lt;code&gt;must/will&lt;/code&gt;，多用 &lt;code&gt;may&lt;/code&gt;, &lt;code&gt;likely&lt;/code&gt;, &lt;code&gt;tend to&lt;/code&gt;。体现学术严谨性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;词汇升级 (Lexical Precision):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Change&lt;/code&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;code&gt;Modify / Alter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Think&lt;/code&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;code&gt;Argue / Contend&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Make&lt;/code&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;code&gt;Generate / Fabricate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;四、 总结&lt;a href=&quot;#四-总结&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;翻译的本质不是 Words to Words，而是 &lt;strong&gt;Meaning to Meaning&lt;/strong&gt;。&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;多用名词&lt;/strong&gt;代替动词。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多用被动&lt;/strong&gt;代替主动。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多用连接词&lt;/strong&gt;显化逻辑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长短结合&lt;/strong&gt;，利用 &lt;code&gt;It&lt;/code&gt; 句型平衡结构。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;</content:encoded></item><item><title>深入理解逻辑与英语中的 “让步” (Concession)</title><link>https://bfmhno3.github.io/posts/understanding-logic-of-concession/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/understanding-logic-of-concession/</guid><description>为什么我们在英语学习中常听到“让步状语从句”？“让步”的逻辑内核究竟是什么？本文通过“弹簧效应”的比喻，解析让步逻辑的本质——“欲扬先抑”。同时探讨了为何英语强调逻辑连接词，以及在沟通、辩论和工程思维中如何利用“让步”来增强论证力度和体现严谨性。</description><pubDate>Tue, 02 Dec 2025 01:30:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;什么是 “让步” 的逻辑内核？&lt;a href=&quot;#什么是-让步-的逻辑内核&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在正常的线性逻辑中，我们通常认为：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;原因 A &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 导致结果 B&lt;/li&gt;
&lt;li&gt;例如：因为下雨了（A），所以我没出门（B）。&lt;span&gt;&lt;span&gt;←\leftarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;←&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 顺理成章。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;但是，世界是复杂的。有时候，原因 A 发生了，它&lt;strong&gt;预期&lt;/strong&gt;的结果 B 缺没有发生，&lt;strong&gt;反而&lt;/strong&gt;发生了相反的结果 C。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;事实 A（本来应该阻碍 C）&lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 依然发生了结果 C。&lt;/li&gt;
&lt;li&gt;例如：虽然下雨了（A），但我还是出门了（C）。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这就是 “让步” 的&lt;strong&gt;本质&lt;/strong&gt;：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Tip&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;承认一个不利于你结论的事实（这就是 “让”，退一步），然后转折，通过强调你的结论依旧成立，来反衬结论的强大（这就是 “步”，进两步）。&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;想象一个弹簧：你把它&lt;strong&gt;压下去（让步）&lt;/strong&gt;，是为了让它&lt;strong&gt;弹得更高（主句）&lt;/strong&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么英语中如此强调 “让步”？&lt;a href=&quot;#为什么英语中如此强调-让步&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在英语学习中经常看到 “&lt;strong&gt;让步状语从句&lt;/strong&gt;”（Adverbial Clause of Concession），是因为英语非常注重&lt;strong&gt;逻辑连接词&lt;/strong&gt;（Connectives）。&lt;/p&gt;&lt;p&gt;在中文里，我们往往靠语境来表达：“下雨了，我还是去了。”（没有关联词，你也懂）。但在英语里，必须用&lt;strong&gt;逻辑连接词&lt;/strong&gt;把这个逻辑关系标出来。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;常见的表达 “让步” 的逻辑连接词&lt;a href=&quot;#常见的表达-让步-的逻辑连接词&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;英语里的 &lt;em&gt;Although&lt;/em&gt;, &lt;em&gt;Though&lt;/em&gt;, &lt;em&gt;Even if&lt;/em&gt;, &lt;em&gt;Despite&lt;/em&gt;, &lt;em&gt;In spite of&lt;/em&gt; 都在大声喊：“注意！下面我要说一个跟前面预期相反的结论了！”&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;经典的 “虽然……但是……” 陷阱&lt;a href=&quot;#经典的-虽然但是-陷阱&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是中国学生最容易错的地方。&lt;/p&gt;&lt;p&gt;中文逻辑： 因为有冲突，所以既要说“虽然”，又要说“但是”来强调冲突。例如：虽然他很穷，但是他很开心。 (双重标记)。&lt;/p&gt;&lt;p&gt;而在英文逻辑中： 只要你用了 “虽然（Although）”，逻辑关系就已经确立了，再加“但是（But）”就是废话（Redundant）。例如：Although he is poor, he is happy. (正确)，而 Although he is poor, but he is happy. (错误)&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么我们需要 “让步”？&lt;a href=&quot;#为什么我们需要-让步&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;你可能会问：“为什么要这么麻烦？直接说结论不就行了吗？”。比如，为什么要说 “虽然我很忙，但我会帮你”？直接说 “我会帮你” 不行吗？运用“让步”逻辑，通常有三个高级目的：&lt;/p&gt;&lt;section&gt;&lt;h3&gt;增强论证的力度（反衬法）&lt;a href=&quot;#增强论证的力度反衬法&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;普通说法： “这个电机性能很好。”（平铺直叙）让步说法： “虽然这个电机体积只有拳头大，但它能输出巨大的扭矩。”&lt;/p&gt;&lt;p&gt;通过承认“体积小”这个通常的劣势，反衬出“扭矩大”这个技术指标的强悍。这就是工程思维中的 Trade-off（权衡）后的亮点。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;展现思维的严谨性（防守反击）&lt;a href=&quot;#展现思维的严谨性防守反击&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在与他人进行辩论时，如果你只说自己对，别人会觉得你偏激。而如果采用让步策略： “我承认（Concede）你的观点 X 在某些情况下是有道理的（让一步），但是，在当前的核心场景下，我的方案 Y 更优。”&lt;/p&gt;&lt;p&gt;以此达到的效果效果是你先封堵了对方的嘴，表明你考虑过反对意见，显得你客观、全面。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;礼貌与情商（缓冲地带）&lt;a href=&quot;#礼貌与情商缓冲地带&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;直接拒绝： “你是错的。”（太生硬），而如果采用让步拒绝： “尽管你的出发点是好的，但这个方案目前不可行。”，以此达到的效果： 先肯定对方的一部分（给面子），再否定核心内容。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;总结&lt;a href=&quot;#总结&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;回到最开始的问题，“让步”到底是什么意思？&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;字面上： 先退一步，承认障碍或对立面的存在。&lt;/li&gt;
&lt;li&gt;逻辑上： 即使有 &lt;span&gt;&lt;span&gt;XX&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;X&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 这个干扰因素，结论 &lt;span&gt;&lt;span&gt;YY&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 依然成立。&lt;/li&gt;
&lt;li&gt;功能上： 它是一种**“欲扬先抑”**的思维工具。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;简而言之：让步，就是为了让你的结论看起来更不容易、更坚定、或者更客观，而故意先承认一些相反事实的逻辑手段。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>CS2 游戏设置优化</title><link>https://bfmhno3.github.io/posts/cs2-optimization-guide/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/cs2-optimization-guide/</guid><description>告别默认设置！本文提供从画面优化、鼠标灵敏度到枪械配装的全方位 CS2 入门指引，助你提升 FPS 与游戏体验。</description><pubDate>Tue, 25 Nov 2025 15:00:00 GMT</pubDate><content:encoded>&lt;p&gt;CS2（Counter-Strike 2）是 Valve 推出的经典第一人称射击游戏，继承了 CS 的核心玩法并全面升级了 Source 2 引擎。&lt;/p&gt;
&lt;p&gt;游戏的默认设置通常并不适合竞技，我们需要做出一些更改。本文设置的核心逻辑是：&lt;strong&gt;流畅度（FPS） &amp;gt; 画面清晰度（能看清敌人） &amp;gt; 画面特效&lt;/strong&gt;。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;启动项设置（Launch Options）&lt;a href=&quot;#启动项设置launch-options&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在 Steam 库中右键 CS2 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 属性 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 通用 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 启动选项，输入以下代码：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-novid&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-nojoy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-consold&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-high&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+fps_max&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;novid&lt;/code&gt;：跳过开场动画，启动更快。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nojoy&lt;/code&gt;：禁用手柄支持，释放少量系统资源。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-console&lt;/code&gt;：开启控制台。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-high&lt;/code&gt;：将 CS2 进程设置为高优先级，&lt;strong&gt;理论上&lt;/strong&gt;能提升性能稳定性。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+fps_max 0&lt;/code&gt;：解除游戏内帧率上限，让显卡全力运行。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;视频设置（Video Settings）&lt;a href=&quot;#视频设置video-settings&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;按 Esc &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 设置 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 视频 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 高级视频&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;增强角色对比度&lt;/strong&gt;（Boost Player Contrast）：【启用】（必开！这能让你在暗处更容易看清敌人，非常重要）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多重采样抗锯齿&lt;/strong&gt;（MSAA）：【4X MSAA】（建议值。如电脑配置较低，可降至 2X。不建议关闭，否则物体边缘锯齿严重，难以看清远处的敌人）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全局阴影效果&lt;/strong&gt;（Global Shadow Quality）：【高】或【中】（尽量要开低，因为这决定了能否看到墙角的敌人影子）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型/贴图细节&lt;/strong&gt;（Shader Detail）：【低】（为了提高 FPS，且画面更干净，减少干扰）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;着色器细节&lt;/strong&gt;（Shader Detail）：【低】（CS2 的水面和燃烧瓶）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;粒子细节&lt;/strong&gt;（Praticle Detail）：【低】（同上）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环境光遮蔽&lt;/strong&gt;（Ambient Occlusion）：【禁用】（一种增加画面深度的特效，但会显著降低 FPS，对竞技无益）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高动态范围&lt;/strong&gt;（HDR）：【性能】（Performance）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NVIDIA Reflex 低延迟&lt;/strong&gt;：【启用 + 增强】（减少鼠标延迟，手感更跟手）&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;常规视频设置：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;显示模式：【全屏】（必须全屏，窗口化会有输入延迟）&lt;/li&gt;
&lt;li&gt;刷新率：选你显示器支持的最高数值（例如 144Hz，240Hz，这在 FPS 游戏中至关重要）&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;鼠标与感度（Mouse &amp;amp; Sensitivity）&lt;a href=&quot;#鼠标与感度mouse--sensitivity&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;核心原则：千万不要用过高的灵敏度&lt;/strong&gt;。低灵敏度能提供更强的稳定性和精准度。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;鼠标 DPI（硬件）：建议设置为 400 或 800（大多数职业选手的选择）&lt;/li&gt;
&lt;li&gt;游戏内灵敏度
&lt;ul&gt;
&lt;li&gt;如果你是 800 DPI，建议灵敏度在 0.8 - 1.2 之间。&lt;/li&gt;
&lt;li&gt;如果你是 1600 DPI，建议灵敏度在 0.4 - 0.6 之间。&lt;/li&gt;
&lt;li&gt;原则：你需要能够用一次从鼠标垫最左滑到最右的操作，在游戏中转身 180 度多一点点即可。太快了你根本瞄不准头。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;变焦灵敏度（狙击镜）：1.00（默认即可）。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;特别提醒&lt;/strong&gt;：请务必在 Windows 系统鼠标设置中，关闭“提高指针精确度”（Mouse Acceleration）。这对形成肌肉记忆非常有害。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;游戏设置（Game Settings）&lt;a href=&quot;#游戏设置game-settings&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;主要调整 HUD（界面）和雷达，让你对战局更加清晰。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;最大匹配延迟&lt;/strong&gt;：100（防止匹配到太远的服务器，太卡没法玩）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;启用开发者控制台（~）&lt;/strong&gt;：【是】（以后你会用到很多指令）。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;雷达/平板（Radar）：非常重要，默认雷达太小了&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;雷达地图定中玩家&lt;/strong&gt;：【是】&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;雷达旋转&lt;/strong&gt;：【是】&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;雷达缩放&lt;/strong&gt;（Radar Scale）：0.40 - 0.50（建议调小一点，这样你能看到大半张地图的信息，防止队友报点你看不到）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HUD 比例&lt;/strong&gt;：可以稍微调小（0.9），让视野更开阔。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;音频设置（Audio）&lt;a href=&quot;#音频设置audio&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;主音量&lt;/strong&gt;：适中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;音频均衡器&lt;/strong&gt;：【清晰】（Crisp）（通常能让脚步声更明显）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L/R 隔离度&lt;/strong&gt;：50% - 70% 之间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;音乐&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;主菜单音量&lt;/strong&gt;：随意。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MVP 音量&lt;/strong&gt;：建议调低，太吵。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;炸弹/人质音量&lt;/strong&gt;（Ten Second Warning）：【10% - 15%】（这个必须开！当音乐响起时，说明炸弹还有 10 秒爆炸，此时如果没有拆弹器就赶紧跑保抢，不要去拆了）。&lt;/li&gt;
&lt;li&gt;其他音乐：全部设为 0（避免干扰听脚步）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;准星推荐（Crosshair）&lt;a href=&quot;#准星推荐crosshair&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;新手不建议用动态准星（走路回扩散的那种），建议用&lt;strong&gt;静态准星&lt;/strong&gt;。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;风格&lt;/strong&gt;：经典（静态）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;颜色&lt;/strong&gt;：绿色、青色或黄色（在地图中对比度高，容易看清）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中心点&lt;/strong&gt;：不建议开&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;推荐代码：按 Esc &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 设置 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 游戏 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 准星 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 分享或导入，粘贴下面的代码：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;ZywOo (世界顶级选手同款，小且精准): &lt;code&gt;CSGO-Qzpx5-BRLwA-xEljr-coqJp-hzrLq&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;简单易用的十字 (适合新手): &lt;code&gt;CSGO-7ANjy-XxRae-25YoE-7Bfnp-QQSFE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;装备（Loadout）&lt;a href=&quot;#装备loadout&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;CS2 采用 5-5-5 装备选择系统，你需要为每个栏位预设好枪械。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;T 方配置（恐怖分子）&lt;a href=&quot;#t-方配置恐怖分子&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;T 的枪械的特点是：&lt;strong&gt;便宜&lt;/strong&gt;、&lt;strong&gt;威力大&lt;/strong&gt;（AK 能一枪爆头）、**适合进攻。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;手枪&lt;/strong&gt;（Pistos）：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Glock-18&lt;/strong&gt;（格洛克，默认）：自带的。跑打精准度高，适合手枪局一群人冲锋。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P250&lt;/strong&gt;：只要 $300，没钱时买一把，近距离大头很痛。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tec-9&lt;/strong&gt;：T 方跑打之王。一定要带！它的射速快、弹匣大、移动射击极其精准。非常适合新手冲锋破点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desert Eagle&lt;/strong&gt;（沙鹰）：$700 的“手炮”。虽然难用，但是还是带上吧，虽然也用不到。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dual Berettas&lt;/strong&gt; (双持贝瑞塔或双枪): 备选，近距离火力压制强。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;中级武器&lt;/strong&gt;（Mid-Tier）：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MAC-10&lt;/strong&gt; (吹风机): 必带。T方经济局神器，跑得飞快，贴脸无敌，而且很便宜。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MP7&lt;/strong&gt;: 稍微贵一点，但比 MAC-10 更稳，适合稍微远一点的距离。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XM1014&lt;/strong&gt; (连喷): 全自动散弹枪，在特定地图（如办公室、炼狱小镇）用来无脑推土。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;步枪&lt;/strong&gt;（Rifles）：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AK-47&lt;/strong&gt;: 核心中的核心。CS2 的灵魂，一枪爆头必杀。必须练好。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Galil AR&lt;/strong&gt;（加利尔/咖喱）: 穷人救星。当你钱不够买 AK 时，$1800 的咖喱是性价比最高的选择，弹匣大（35发），容错率高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SG 553&lt;/strong&gt;: 带瞄准镜的步枪。虽然射速慢，但架点稳，打远距离比 AK 容易。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSG 08&lt;/strong&gt;（鸟狙）: 便宜的狙击枪，移动速度快。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWP&lt;/strong&gt;（大狙）: 必须带，哪怕你不玩，也要帮队友买。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;CT 方配置（反恐精英）&lt;a href=&quot;#ct-方配置反恐精英&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;CT 的枪械特点是：&lt;strong&gt;精准&lt;/strong&gt;、&lt;strong&gt;消音&lt;/strong&gt;、&lt;strong&gt;适合架点防守&lt;/strong&gt;，但普遍较贵，且主战步枪不能一枪爆头（有头盔时）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;手枪&lt;/strong&gt;（Pistols）：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;USP-S&lt;/strong&gt;（默认）: 推荐选 USP 而不是 P2000。USP 有消音器，且第一发非常准，适合远距离点名。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P250&lt;/strong&gt;: 经济局混起专用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Five-SeveN&lt;/strong&gt;（57/FN57）: CT方近战神枪。一定要带（替换掉 CZ75）。它的穿甲能力极强，弹匣大，当 T rush 过来时，这把手枪经常能刷屏。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desert Eagle&lt;/strong&gt;: 同上。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;中级武器&lt;/strong&gt;（Mid-Tier）：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MP9&lt;/strong&gt;（小蜜蜂）: CT方最强冲锋枪。射速极快，是反制 T Rush 的神器。跑着打、跳着打都很准。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MAG-7&lt;/strong&gt;（警喷）: 只有 5 发子弹，但它是唯一一把跳起来打依然精准的喷子，非常适合守门口。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;步枪&lt;/strong&gt;（Rifles）：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;M4A1-S&lt;/strong&gt; (消音M4): 新手强烈推荐，&lt;strong&gt;后坐力极小&lt;/strong&gt;，非常好压枪。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FAMAS&lt;/strong&gt; (法玛斯): CT 的经济步枪。当你钱不够买 M4 时只能买它，虽然手感一般，但还得带。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AUG&lt;/strong&gt;: 带镜子的 M4。当你守长距离点位（如 Dust2 A大）时，开镜很稳。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWP&lt;/strong&gt;: 防守方的威慑力核心。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSG 08&lt;/strong&gt;: 鸟狙。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;其他&lt;a href=&quot;#其他&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PP-Bizon&lt;/strong&gt;（野牛）：非常推荐，跑打真的很爽，建议多玩。只要第一局什么都不起（不买甲，不买道具），哪怕手枪局输了，后面也能把把起全甲 + 野牛。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;M249&lt;/strong&gt;：价格昂贵（$5200），性能笨重，实战性价比极低。不建议新手购买，有这钱不如给大哥发把大狙，再给自己买把野牛冲前面给大哥拿信息。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>小米手机 (HyperOS/MIUI) 深度优化指南：移除 AI 组件与去广告</title><link>https://bfmhno3.github.io/posts/xiaomi-debloat-guide/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/xiaomi-debloat-guide/</guid><description>通过 ADB 移除小米手机中冗余的 AI 组件、广告服务及臃肿软件，并调整系统设置以提升流畅度和续航。</description><pubDate>Tue, 25 Nov 2025 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;随着 HyperOS 和 MIUI 的更新，系统集成了越来越多的 AI 功能和后台服务。虽然部分功能很实用，但对于追求极致流畅、隐私保护或老机型用户来说，这些组件可能意味着额外的耗电和卡顿。&lt;/p&gt;
&lt;p&gt;本文将介绍如何通过 ADB (Android Debug Bridge) 安全地移除这些组件，并进行系统级优化。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;通过 ADB 卸载 AI 及冗余组件&lt;a href=&quot;#通过-adb-卸载-ai-及冗余组件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;此操作&lt;strong&gt;不需要 ROOT 权限&lt;/strong&gt;，但需要一台电脑（Windows/Mac/Linux 均可）。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;1. 准备工作&lt;a href=&quot;#1-准备工作&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;开启开发者模式&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;进入&lt;strong&gt;设置&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;我的设备&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;全部参数&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;连续点击“OS 版本号”约 5-7 次，直到提示“您已处于开发者模式”。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;开启 USB 调试&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;进入&lt;strong&gt;设置&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;更多设置&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;开发者选项&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;开启**“USB 调试”**。&lt;/li&gt;
&lt;li&gt;开启 &lt;strong&gt;“USB 调试（安全设置）”&lt;/strong&gt;（重要：如果不开启此项，部分系统应用无法被卸载，提示 &lt;code&gt;Permission denied&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;连接电脑&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用数据线连接手机与电脑。&lt;/li&gt;
&lt;li&gt;在电脑上下载并解压 &lt;a href=&quot;https://developer.android.com/studio/releases/platform-tools&quot; target=&quot;_blank&quot;&gt;SDK Platform Tools&lt;/a&gt; (ADB 工具包)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. 执行卸载命令&lt;a href=&quot;#2-执行卸载命令&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;在电脑终端（CMD 或 Powershell）进入 ADB 工具目录，输入以下命令确认连接：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;adb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;devices&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如果手机弹出授权窗口，请点击“允许”。看到设备序列号后，即可使用以下格式卸载应用：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;adb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;shell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;uninstall&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--user&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;包名&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;code&gt;--user 0&lt;/code&gt; 表示仅针对当前用户卸载，不会破坏系统分区。如果出现问题，可以通过恢复出厂设置完全还原。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3. 建议卸载的清单&lt;a href=&quot;#3-建议卸载的清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;以下是小米系统中常见的 AI 相关及“臃肿”组件的包名。请根据你的需求&lt;strong&gt;选择性&lt;/strong&gt;执行：&lt;/p&gt;&lt;section&gt;&lt;h4&gt;🤖 核心 AI 与语音组件&lt;a href=&quot;#-核心-ai-与语音组件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;如果你不使用小爱同学，这些服务在后台占用大量资源。&lt;/p&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;组件名称&lt;/th&gt;&lt;th&gt;包名&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;小爱同学&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.miui.voiceassist&lt;/code&gt;&lt;/td&gt;&lt;td&gt;语音助手主程序&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;小米 AI 引擎&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.xiaomi.aiasst.service&lt;/code&gt;&lt;/td&gt;&lt;td&gt;场景识别、AI 调度&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;AI 视觉/传送门&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.miui.contentextension&lt;/code&gt;&lt;/td&gt;&lt;td&gt;屏幕识别、选词搜索&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;小爱建议/负一屏&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.miui.personalassistant&lt;/code&gt;&lt;/td&gt;&lt;td&gt;桌面左侧的负一屏&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;小爱翻译&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.xiaomi.scanner&lt;/code&gt;&lt;/td&gt;&lt;td&gt;包含扫一扫和翻译功能&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;📢 广告与分析组件 (推荐删除)&lt;a href=&quot;#-广告与分析组件-推荐删除&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;这些组件主要用于推送广告和收集数据，删除后系统更清爽。&lt;/p&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;组件名称&lt;/th&gt;&lt;th&gt;包名&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;系统广告服务 (MSA)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.miui.systemAdSolution&lt;/code&gt;&lt;/td&gt;&lt;td&gt;MIUI System Ads，广告核心&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;小米分析&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.miui.analytics&lt;/code&gt;&lt;/td&gt;&lt;td&gt;数据采集与分析&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;用户反馈&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.miui.miservice&lt;/code&gt;&lt;/td&gt;&lt;td&gt;反馈服务&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;智能服务&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.miui.hybrid&lt;/code&gt;&lt;/td&gt;&lt;td&gt;混合推送服务&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;快应用服务框架&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.miui.hybrid.accessory&lt;/code&gt;&lt;/td&gt;&lt;td&gt;经常误触打开快应用&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;📦 其他常见臃肿软件&lt;a href=&quot;#-其他常见臃肿软件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;组件名称&lt;/th&gt;&lt;th&gt;包名&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;小米浏览器&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.android.browser&lt;/code&gt;&lt;/td&gt;&lt;td&gt;建议使用 Chrome 或 Edge 替代&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;小米视频&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.miui.video&lt;/code&gt;&lt;/td&gt;&lt;td&gt;包含大量在线内容&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;小米音乐&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.miui.player&lt;/code&gt;&lt;/td&gt;&lt;td&gt;QQ 音乐简化版，可以试一试&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;小米钱包&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;com.mipay.wallet&lt;/code&gt;&lt;/td&gt;&lt;td&gt;如果不用 NFC 刷卡可删&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;4. 如何恢复误删的应用？&lt;a href=&quot;#4-如何恢复误删的应用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;如果你不小心删除了重要组件，可以通过以下命令恢复（无需重置手机）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;adb&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;shell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cmd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install-existing&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;包名&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;例如恢复小爱同学：&lt;code&gt;adb shell cmd package install-existing com.miui.voiceassist&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;5. 推荐工具：UAD&lt;a href=&quot;#5-推荐工具uad&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;如果你觉得敲命令太麻烦，可以使用开源图形化工具 &lt;strong&gt;&lt;a href=&quot;https://github.com/0x192/universal-android-debloater&quot; target=&quot;_blank&quot;&gt;UAD (Universal Android Debloater)&lt;/a&gt;&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;UAD 是一个跨平台的图形界面软件，能自动识别小米手机的臃肿软件，并标注“安全删除”、“谨慎删除”，非常适合新手。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;⚠️ 警告&lt;/strong&gt;：绝对不要卸载 &lt;code&gt;com.miui.home&lt;/code&gt;（系统桌面）或 &lt;code&gt;com.android.systemui&lt;/code&gt;，否则手机会黑屏或无法进入系统。&lt;/p&gt;&lt;/blockquote&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;系统设置优化&lt;a href=&quot;#系统设置优化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;除了卸载软件，正确的设置也能显著提升体验。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;1. 关闭内存扩展 (Memory Extension)&lt;a href=&quot;#1-关闭内存扩展-memory-extension&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;路径&lt;/strong&gt;：&lt;strong&gt;设置&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;更多设置&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;内存扩展&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;建议&lt;/strong&gt;：&lt;strong&gt;关闭&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原因&lt;/strong&gt;：这个功能使用闪存（Flash Storage）模拟内存（SWAP）。手机闪存的随机读写速度远低于物理内存（RAM）。开启后不仅不会让手机变快，反而会增加 I/O 负担，导致系统在切换应用时掉帧，并增加发热和耗电。对于 8GB 及以上内存的机型，此功能完全是副作用。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. 关闭系统广告开关&lt;a href=&quot;#2-关闭系统广告开关&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;虽然卸载了 MSA，但部分开关仍建议手动关闭以防漏网之鱼。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;系统工具广告&lt;/strong&gt;：&lt;strong&gt;设置&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;账号&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;关于小米账号&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;系统广告&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 关闭。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用内推荐&lt;/strong&gt;：分别进入&lt;strong&gt;天气、日历、下载管理、应用商店、文件管理&lt;/strong&gt;的设置中，找到并关闭“内容推荐”、“个性化服务”或“资讯”。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3. 调整动画速度&lt;a href=&quot;#3-调整动画速度&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;路径&lt;/strong&gt;：&lt;strong&gt;开发者选项&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 向下滚动找到&lt;strong&gt;绘图&lt;/strong&gt;栏目。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设置&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;窗口动画缩放 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;0.5x&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;过渡动画缩放 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;0.5x&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Animator 时长缩放 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;0.5x&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;效果&lt;/strong&gt;：这不会改变手机的实际处理速度，但会缩短 UI 动画的持续时间，让手机在体感上响应更加迅速干脆。&lt;/li&gt;
&lt;/ul&gt;&lt;hr /&gt;&lt;p&gt;*免责声明：本文提供的操作涉及系统层面的修改。虽然通过 &lt;code&gt;pm uninstall --user 0&lt;/code&gt; 卸载应用通常是安全的且可逆的，但操作不当仍可能导致系统不稳定。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>在 Windows 上使用 Docker、Git Bash 和 overleaf-toolkit 部署 Overleaf 社区版</title><link>https://bfmhno3.github.io/posts/overleaf-community-windows/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/overleaf-community-windows/</guid><pubDate>Sat, 18 Oct 2025 14:30:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;0. 前言&lt;a href=&quot;#0-前言&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://www.overleaf.com/&quot; target=&quot;_blank&quot;&gt;Overleaf&lt;/a&gt; 是一个广受欢迎的在线 LaTeX 编辑器，它极大地简化了 LaTeX 的使用门槛。虽然官方提供了在线服务，但是处于数据隐私、网络稳定或自定义需求，我们常常希望搭建一个私有的 Overleaf 实例。Overleaf 官方提供了基于 Docker 的 &lt;code&gt;toolkit&lt;/code&gt; 工具来简化社区版的部署流程。&lt;/p&gt;&lt;p&gt;然而，官方的 &lt;code&gt;toolkit&lt;/code&gt; 主要面向 Linux/macOS 环境，在 Windows 上通过 Git Bash 执行时会遇到一个棘手的路径问题。本教程将详细记录如何在 Windows 11/10 环境下，借助 Docker Desktop 和 Git Bash，成功部署 Overleaf 社区版，并提供关键的脚本修复方案。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;1. 环境准备&lt;a href=&quot;#1-环境准备&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在开始之前，请确保你的系统已安装并正确配置了以下软件：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Windows 10/11：需开启 Hyper-V 或 WSL 2 功能&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.docker.com/products/docker-desktop/&quot; target=&quot;_blank&quot;&gt;Docker Desktop for Windows&lt;/a&gt;：建议使用 WSL 2 后端，性能更佳。安装后请确保 Docker 服务已正常启动。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/downloads/win&quot; target=&quot;_blank&quot;&gt;Git for Windows&lt;/a&gt;: 安装时请务必勾选 &lt;code&gt;Git Bash Here&lt;/code&gt; 选项，我们将使用它来执行 shell 脚本。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;笔者环境参考&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;OS: Windows 11 Professional 24H2&lt;/li&gt;
&lt;li&gt;Docker: 28.5.1&lt;/li&gt;
&lt;li&gt;Git: 2.51.0.windows.2&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2. 部署步骤&lt;a href=&quot;#2-部署步骤&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;第一步：克隆 Overleaf Toolkit 仓库&lt;a href=&quot;#第一步克隆-overleaf-toolkit-仓库&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;首先，打开 Git Bash，选择一个你喜欢的工作目录（例如 &lt;code&gt;D:\&lt;/code&gt;），然后执行以下命令克隆官方的 &lt;code&gt;toolkit&lt;/code&gt; 仓库：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 进入 D:\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/d&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 克隆官方仓库&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clone&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;https://github.com/overleaf/toolkit.git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./overleaf-toolkit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./overleaf-toolkit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;第二步：修复 Windows 路径兼容性问题&lt;a href=&quot;#第二步修复-windows-路径兼容性问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是整个教程最核心的一步。官方的 &lt;code&gt;docker-compose&lt;/code&gt; 脚本在生成容器挂载路径时，没有正确处理 Git Bash 在 Windows 上的路径格式，导致 Docker 无法找到数据卷。&lt;/p&gt;&lt;p&gt;我已就此问题向官方提交了 &lt;a href=&quot;https://github.com/overleaf/toolkit/issues/379&quot; target=&quot;_blank&quot;&gt;Issue #379&lt;/a&gt;，并提交了修复方案 &lt;a href=&quot;https://github.com/overleaf/toolkit/pull/380&quot; target=&quot;_blank&quot;&gt;PR #380&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;鉴于此 PR 还并未被合并到主分支，需要用户自行进行修改。&lt;/p&gt;&lt;p&gt;解决方案如下：&lt;/p&gt;&lt;p&gt;使用你喜欢的代码编辑器（如 VS Code 或者记事本）打开 &lt;code&gt;bin/docker-compose&lt;/code&gt; 文件。&lt;/p&gt;&lt;p&gt;找到 &lt;code&gt;set_base_vars()&lt;/code&gt; 函数，其中有两次对 &lt;code&gt;OVERLEAF_IN_CONTAINER_DATA_PATH&lt;/code&gt; 变量的赋值。我们需要在路径的开头额外添加一个斜杠 &lt;code&gt;/&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;修改前：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;OVERLEAF_IN_CONTAINER_DATA_PATH&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;/var/lib/overleaf&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;修改后：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;OVERLEAF_IN_CONTAINER_DATA_PATH&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;//var/lib/overleaf&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;注意：此函数有&lt;strong&gt;两处&lt;/strong&gt; &lt;code&gt;OVERLEAF_IN_CONTAINER_DATA_PATH=/var/lib/overleaf&lt;/code&gt;，请务必全部修改。这个修改的本质是帮助 Git Bash/MSYS2 正确地将 POSIX 风格的路径 &lt;code&gt;/var/lib/overleaf&lt;/code&gt; 转换为 Docker 能理解的 Windows 路径。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;第三步：初始化并启动 Overleaf 服务&lt;a href=&quot;#第三步初始化并启动-overleaf-服务&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;修改脚本后，我们回到 Git Bash 终端，继续在 &lt;code&gt;overleaf-toolkit&lt;/code&gt; 目录下执行命令。&lt;/p&gt;&lt;section&gt;&lt;h4&gt;初始化 Overleaf 配置&lt;a href=&quot;#初始化-overleaf-配置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;执行 &lt;code&gt;init&lt;/code&gt; 命令，该命令会生成初始化配置文件，例如 &lt;code&gt;config/overleaf.rc&lt;/code&gt;、&lt;code&gt;config/variables.env&lt;/code&gt;、&lt;code&gt;config/version&lt;/code&gt; 等。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;./bin/init&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;启动 Overleaf 容器服务&lt;a href=&quot;#启动-overleaf-容器服务&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;初始化完成后，执行 &lt;code&gt;up&lt;/code&gt; 命令来启动所有服务。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;./bin/up&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;提示：首次执行时，由于需要下载多个 Docker 镜像，此过程可能会持续较长时间，具体取决于你的网络速度。请耐心等待，确保所有镜像都成功拉取。&lt;/p&gt;&lt;p&gt;执行成功后，你将看到大量的服务日志输出。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;第四步：首次访问与管理员配置&lt;a href=&quot;#第四步首次访问与管理员配置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;服务启动后，打开你的浏览器，访问 &lt;code&gt;http://127.0.0.1&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;首次访问时，你会被引导至管理员账户创建页面。请根据提示设置你的第一个管理员用户的邮箱和密码。创建成功后，你就可以使用这个账户登录并开始使用你的私有 Overleaf 实例了！&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;3. 日常管理&lt;a href=&quot;#3-日常管理&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在日常使用中，你可能会需要启动或停止 Overleaf 服务。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;停止服务：在 &lt;code&gt;overleaf-toolkit&lt;/code&gt; 目录下打开 Git Bash，执行：&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;./bin/stop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;重新启动服务：同样，在 &lt;code&gt;overleaf-toolkit&lt;/code&gt; 目录下执行：&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;./bin/up&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;总结&lt;a href=&quot;#总结&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;通过对 &lt;code&gt;bin/docker-compose&lt;/code&gt; 脚本进行一个小小的修改，我们成功解决了 &lt;code&gt;overleaf-toolkit&lt;/code&gt; 在 Windows + Git Bash 环境下的路径兼容性问题，顺利地部署了 Overleaf 社区版。自托管 Overleaf 不仅保障了数据安全，还提供了更自由的定制空间。&lt;/p&gt;&lt;p&gt;希望这篇教程能帮助到同样遇到此问题的同学。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Notes of Effective CMake</title><link>https://bfmhno3.github.io/posts/notes-of-effective-cmake/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/notes-of-effective-cmake/</guid><pubDate>Fri, 08 Aug 2025 15:17:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;1. The Philosophy of Modern CMake&lt;a href=&quot;#1-the-philosophy-of-modern-cmake&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;Why “Effective CMake”?&lt;a href=&quot;#why-effective-cmake&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Just like with C++, the way you write CMake code significantly impacts your project’s maintainability, ease of use for others, and scalability. Adopting modern practices is key.&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;CMake is Code&lt;a href=&quot;#cmake-is-code&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Treat your &lt;code&gt;CMakeLists.txt&lt;/code&gt; files with the same care as your source code. Apply principles like Don’t Repeat Yourself (DRY), keep it clean, and write comments where necessary.&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2. The CMake Language: A Quick Tour&lt;a href=&quot;#2-the-cmake-language-a-quick-tour&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;Organization&lt;a href=&quot;#organization&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;CMake code can be organized in three ways:&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Directories (&lt;code&gt;CMakeLists.txt&lt;/code&gt;)&lt;/strong&gt;: The entry point for a project or sub-project. &lt;code&gt;add_subdirectory()&lt;/code&gt; adds another directory (which must contain a &lt;code&gt;CMakeLists.txt&lt;/code&gt;) to the build.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scripts (&lt;code&gt;&amp;lt;name&amp;gt;.cmake&lt;/code&gt;)&lt;/strong&gt;: Executed with &lt;code&gt;cmake -P &amp;lt;script&amp;gt;.cmake&lt;/code&gt;. They are useful for automation but cannot define build targets like executables or libraries. In other words, &lt;em&gt;not all commands are supported&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modules (&lt;code&gt;&amp;lt;name&amp;gt;.cmake&lt;/code&gt;)&lt;/strong&gt;: Reusable code included in your projects via &lt;code&gt;include()&lt;/code&gt;. They are located in the &lt;code&gt;CMAKE_MODULE_PATH&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Commands&lt;a href=&quot;#commands&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;CMake commands are &lt;strong&gt;case-insensitive&lt;/strong&gt;, but their arguments (including variable names) are &lt;strong&gt;case-sensitive&lt;/strong&gt;.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# command_name(ARGUMENT1 ARGUMENT2 ...)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;project&lt;/span&gt;&lt;span&gt;(MyProject VERSION 1.0)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Caution&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Command invovations are not expressions&lt;/strong&gt;, so you cannnot put a command invocation directly as an argument of another command or inside an if condition. They are not possible.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Variables&lt;a href=&quot;#variables&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Set a variable&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(MY_VARIABLE &lt;/span&gt;&lt;span&gt;&quot;Hello&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Reference a variable (dereference)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;(STATUS &lt;/span&gt;&lt;span&gt;&quot;My variable is: ${MY_VARIABLE}&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Unset a variable&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;unset&lt;/span&gt;&lt;span&gt;(MY_VARIABLE)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;
&lt;li&gt;In CMake, everything is a string. Lists are just strings separated by semicolons &lt;code&gt;;&lt;/code&gt; (e.g., &lt;code&gt;&quot;item1;item2;item3&quot;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An unset or undefined variable expands to an empty string&lt;/strong&gt;. This can be a common source of bugs! Use &lt;code&gt;if(DEFINED VAR_NAME)&lt;/code&gt; to check if a variable is set.&lt;/li&gt;
&lt;li&gt;CMake variables are not environment variables (unlink &lt;code&gt;Makefile&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;WARNING&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Avoid custom variables in the arguments of project commands.&lt;/p&gt;&lt;p&gt;In modern CMake, what we need is targets and properties. See them later.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Comments&lt;a href=&quot;#comments&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# This is a single-line comment.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#[=[&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;This is a multi-line comment.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;It can contain other symbols and even # characters.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#]=]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Generator Expressions: The &lt;code&gt;$&amp;lt;...&amp;gt;&lt;/code&gt; Syntax&lt;a href=&quot;#generator-expressions-the--syntax&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Generator expressions, often called “genex,” are a powerful CMake feature that uses the &lt;code&gt;$&amp;lt;...&amp;gt;&lt;/code&gt; syntax. They are &lt;strong&gt;not&lt;/strong&gt; evaluated when CMake first reads your &lt;code&gt;CMakeLists.txt&lt;/code&gt;. Instead, they are written into the native build files (like Makefiles or Visual Studio projects) and are evaluated &lt;strong&gt;during the build process&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;This delayed evaluation is crucial because it allows you to create build configurations that are aware of things that are only known at build time, such as the specific build type (&lt;code&gt;Debug&lt;/code&gt;, &lt;code&gt;Release&lt;/code&gt;), the compiler being used, or the language of a source file.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Think of generator expressions as placeholders that the final build tool (like Make, Ninja, or MSBuild) will fill in with the correct value at the right time. This is much more flexible than using &lt;code&gt;if()&lt;/code&gt; statements in CMake, which are only evaluated once when you run &lt;code&gt;cmake&lt;/code&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;section&gt;&lt;h4&gt;Common Use Cases and Examples&lt;a href=&quot;#common-use-cases-and-examples&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Conditional Compilation Definitions (&lt;code&gt;$&amp;lt;CONFIG:...&amp;gt;&lt;/code&gt;)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;This is the most common use case. You want to define a preprocessor macro differently for &lt;code&gt;Debug&lt;/code&gt; and &lt;code&gt;Release&lt;/code&gt; builds.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# In Debug mode, VERBOSITY will be 2. In all other modes (e.g., Release), it will be 0.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_compile_definitions&lt;/span&gt;&lt;span&gt;(my_app PRIVATE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;VERBOSITY=$&amp;lt;IF:$&amp;lt;CONFIG:Debug&amp;gt;,2,0&amp;gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;$&amp;lt;IF:condition,true_value,false_value&amp;gt;&lt;/code&gt; expression is evaluated at build time. If the configuration is &lt;code&gt;Debug&lt;/code&gt;, it resolves to &lt;code&gt;VERBOSITY=2&lt;/code&gt;; otherwise, it becomes &lt;code&gt;VERBOSITY=0&lt;/code&gt;.&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Conditional Include Directories (&lt;code&gt;$&amp;lt;BUILD_INTERFACE:...&amp;gt;&lt;/code&gt; and &lt;code&gt;$&amp;lt;INSTALL_INTERFACE:...&amp;gt;&lt;/code&gt;)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;A library often has different include paths when being built inside a project versus when it’s installed on a system.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_include_directories&lt;/span&gt;&lt;span&gt;(my_lib PUBLIC&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# When my_lib is built as part of this project, use the source directory.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;BUILD_INTERFACE:&lt;/span&gt;&lt;span&gt;${CMAKE_CURRENT_SOURCE_DIR}&lt;/span&gt;&lt;span&gt;/include&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# When an external project uses an installed version of my_lib, the path is just &apos;include&apos;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;INSTALL_INTERFACE:include&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;This ensures your library is usable both during development and after deployment without any changes.&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Compiler-Specific Flags (&lt;code&gt;$&amp;lt;CXX_COMPILER_ID:...&amp;gt;&lt;/code&gt;)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;You can pass specific flags depending on the compiler being used (e.g., GCC, Clang, MSVC).&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Enable strong warnings, but use the correct flag for each compiler.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(GCC_CLANG_WARNINGS &lt;/span&gt;&lt;span&gt;&quot;-Wall -Wextra -Wpedantic&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(MSVC_WARNINGS &lt;/span&gt;&lt;span&gt;&quot;/W4&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_compile_options&lt;/span&gt;&lt;span&gt;(my_app PRIVATE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;$&amp;lt;IF:$&amp;lt;CXX_COMPILER_ID:MSVC&amp;gt;,${MSVC_WARNINGS},${GCC_CLANG_WARNINGS}&amp;gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Language-Specific Standards (&lt;code&gt;$&amp;lt;COMPILE_LANGUAGE:...&amp;gt;&lt;/code&gt;)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;If your target mixes C and C++ code, you can set standards for each language.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_compile_features&lt;/span&gt;&lt;span&gt;(my_app PRIVATE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# Set C++ standard to 17 for all C++ files.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;COMPILE_LANGUAGE:CXX&amp;gt;:cxx_std_17&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# Set C standard to 11 for all C files.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;COMPILE_LANGUAGE:C&amp;gt;:c_std_11&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Generator expressions are a cornerstone of modern, robust, and portable CMake scripts. Mastering them allows you to write cleaner and more powerful &lt;code&gt;CMakeLists.txt&lt;/code&gt; files.&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Custom Commands: &lt;code&gt;function()&lt;/code&gt; vs. &lt;code&gt;macro()&lt;/code&gt;&lt;a href=&quot;#custom-commands-function-vs-macro&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;You can create your own commands to reduce code duplication.&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;function()&lt;/code&gt;&lt;/strong&gt;: Creates a new variable scope. To pass results back to the caller, you must use &lt;code&gt;set(... PARENT_SCOPE)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;macro()&lt;/code&gt;&lt;/strong&gt;: Does not create a new scope. It performs simple text replacement, much like a C preprocessor macro.&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;TIP&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Rule of Thumb:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;function()&lt;/code&gt; by default to avoid polluting the caller’s scope with side effects.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;macro()&lt;/code&gt; only when you need to wrap a command that has an output parameter or when you explicitly want side effects in the caller’s scope.&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;3. The Core of Modern CMake: Targets and Properties&lt;a href=&quot;#3-the-core-of-modern-cmake-targets-and-properties&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Modern CMake revolves around &lt;strong&gt;targets&lt;/strong&gt; and their &lt;strong&gt;properties&lt;/strong&gt;. A target can be an executable, a library, or a custom target.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;WARNING&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Avoid directory-level commands like &lt;code&gt;include_directories()&lt;/code&gt;, &lt;code&gt;link_libraries()&lt;/code&gt;, and &lt;code&gt;add_compile_options()&lt;/code&gt;. They use global state and make dependencies hard to reason about. Always prefer the &lt;code&gt;target_*&lt;/code&gt; equivalents.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;WARNING&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Don’t use &lt;code&gt;file(GLOB)&lt;/code&gt; in your &lt;code&gt;CMakeLists.txt&lt;/code&gt; anymore.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;section&gt;&lt;h3&gt;Thinking in Targets&lt;a href=&quot;#thinking-in-targets&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Imagine targets as objects in an OOP language.&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Constructor&lt;/strong&gt;: &lt;code&gt;add_executable()&lt;/code&gt;, &lt;code&gt;add_library()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Member Functions&lt;/strong&gt;: &lt;code&gt;target_sources()&lt;/code&gt;, &lt;code&gt;target_include_directories()&lt;/code&gt;, &lt;code&gt;target_link_libraries()&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Member Variables&lt;/strong&gt;: Properties like &lt;code&gt;VERSION&lt;/code&gt;, &lt;code&gt;SOURCES&lt;/code&gt;, &lt;code&gt;INTERFACE_INCLUDE_DIRECTORIES&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Create a library target&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;add_library&lt;/span&gt;&lt;span&gt;(my_lib STATIC my_lib.cpp my_lib.h)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Add properties to the target&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_include_directories&lt;/span&gt;&lt;span&gt;(my_lib PUBLIC include)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_compile_features&lt;/span&gt;&lt;span&gt;(my_lib PUBLIC cxx_std_17)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Build Specifications vs. Usage Requirements&lt;a href=&quot;#build-specifications-vs-usage-requirements&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Non-INTERFACE_&lt;/code&gt; properties define the build specification of a terget&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INTERFACE_&lt;/code&gt; properties define the usage requirements of a target.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;This is the most critical concept in modern CMake. When you link a library, the consumer needs to know its include directories, compile definitions, etc.&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PRIVATE&lt;/code&gt;&lt;/strong&gt;: The property is only for building this target. It is not passed on to consumers (&lt;code&gt;Non-INTERFACE_&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;INTERFACE&lt;/code&gt;&lt;/strong&gt;: The property is &lt;em&gt;only&lt;/em&gt; for consumers of this target. The target itself doesn’t use it for its own build. This is perfect for header-only libraries (&lt;code&gt;INTERFACE_&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PUBLIC&lt;/code&gt;&lt;/strong&gt;: The property is for both the target’s build (&lt;code&gt;PRIVATE&lt;/code&gt;) and for its consumers (&lt;code&gt;INTERFACE&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;&lt;section&gt;&lt;h4&gt;Example&lt;a href=&quot;#example&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# A logging library that uses spdlog internally&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;add_library&lt;/span&gt;&lt;span&gt;(my_logger my_logger.cpp)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# my_logger needs the spdlog headers to compile.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Anyone who uses my_logger does NOT need spdlog headers directly.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_include_directories&lt;/span&gt;&lt;span&gt;(my_logger&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PRIVATE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;${spdlog_SOURCE_DIR}&lt;/span&gt;&lt;span&gt;/include&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Anyone who uses my_logger needs my_logger&apos;s own headers.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_include_directories&lt;/span&gt;&lt;span&gt;(my_logger&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PUBLIC&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;BUILD_INTERFACE:&lt;/span&gt;&lt;span&gt;${CMAKE_CURRENT_SOURCE_DIR}&lt;/span&gt;&lt;span&gt;/include&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;INSTALL_INTERFACE:include&amp;gt; &lt;/span&gt;&lt;span&gt;# Different path after installation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Header-Only Libraries&lt;a href=&quot;#header-only-libraries&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;For libraries that are headers-only, create an &lt;code&gt;INTERFACE&lt;/code&gt; library. It has no sources and only defines usage requirements.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;add_library&lt;/span&gt;&lt;span&gt;(my_header_lib INTERFACE)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_include_directories&lt;/span&gt;&lt;span&gt;(my_header_lib INTERFACE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;BUILD_INTERFACE:&lt;/span&gt;&lt;span&gt;${CMAKE_CURRENT_SOURCE_DIR}&lt;/span&gt;&lt;span&gt;/include&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&amp;lt;INSTALL_INTERFACE:include&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;WARNING&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;The library interface may change during installation. Use the &lt;code&gt;BUILD_INTERFACE&lt;/code&gt; and &lt;code&gt;INSTALL_INTERFACE&lt;/code&gt; generator expressions as filters&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;4. Working with Dependencies&lt;a href=&quot;#4-working-with-dependencies&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;Finding Packages with &lt;code&gt;find_package()&lt;/code&gt;&lt;a href=&quot;#finding-packages-with-find_package&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;This is the standard way to find and use external libraries that have been installed on the system or are part of the project.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Find the Boost library, version 1.71 or newer.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# It is REQUIRED; CMake will fail if it&apos;s not found.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;find_package&lt;/span&gt;&lt;span&gt;(Boost 1.71 REQUIRED COMPONENTS &lt;/span&gt;&lt;span&gt;system&lt;/span&gt;&lt;span&gt; thread)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# If found, use the imported target provided by Boost&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;(Boost_FOUND)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;target_link_libraries&lt;/span&gt;&lt;span&gt;(my_app PRIVATE Boost::&lt;/span&gt;&lt;span&gt;system&lt;/span&gt;&lt;span&gt; Boost::thread)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;endif&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Regardless of the mode/package used, a &lt;code&gt;&amp;lt;PackageName&amp;gt;_FOUND&lt;/code&gt; variable will be set to indicate whether the package was found.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Always use the official, namespaced, imported targets (e.g., &lt;code&gt;Boost::system&lt;/code&gt;, &lt;code&gt;Qt5::Core&lt;/code&gt;, &lt;code&gt;GTest::GTest&lt;/code&gt;). Never use the old-style &lt;code&gt;_LIBRARIES&lt;/code&gt; and &lt;code&gt;_INCLUDE_DIRS&lt;/code&gt; variables. Imported targets handle all dependency properties for you automatically.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Use a Find module for third party libraries that are not built with CMake that don’t support clients to use CMake. Also, report this as a bug to their authors.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;If you need to write a find module for a third-party library, report this as a bug to the authors. Because most people use CMake, it’s a problem that don’t support it.&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Fetching Dependencies at Configure Time with &lt;code&gt;FetchContent&lt;/code&gt;&lt;a href=&quot;#fetching-dependencies-at-configure-time-with-fetchcontent&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;For dependencies that you want to download and build alongside your project, &lt;code&gt;FetchContent&lt;/code&gt; is the modern, preferred approach. It’s great for ensuring all developers use the exact same version of a dependency.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;include&lt;/span&gt;&lt;span&gt;(FetchContent)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Declare the dependency&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;FetchContent_Declare(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;googletest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;GIT_REPOSITORY https://github.com/google/googletest.git&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;GIT_TAG        release-1.12.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Make it available (downloads and adds it as a sub-project)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;FetchContent_MakeAvailable(googletest)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Now you can link to it just like any other target in your project&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_link_libraries&lt;/span&gt;&lt;span&gt;(my_tests PRIVATE GTest::GTest GTest::Main)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;code&gt;FetchContent&lt;/code&gt; is generally preferred over Git submodules because it gives the parent project more control and is easier to manage.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Exporting Your Project as a Package&lt;a href=&quot;#exporting-your-project-as-a-package&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;When you want other projects to use your library with &lt;code&gt;find_package()&lt;/code&gt;, you need to generate package configuration files.&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Install Targets&lt;/strong&gt;: Use &lt;code&gt;install(TARGETS ...)&lt;/code&gt; to specify where your library files (&lt;code&gt;.a&lt;/code&gt;, &lt;code&gt;.so&lt;/code&gt;, &lt;code&gt;.dll&lt;/code&gt;) and headers should be installed. Use the &lt;code&gt;EXPORT&lt;/code&gt; keyword to associate them with a target export set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Install Export Set&lt;/strong&gt;: Use &lt;code&gt;install(EXPORT ...)&lt;/code&gt; to create a &lt;code&gt;&amp;lt;name&amp;gt;Targets.cmake&lt;/code&gt; file. This file contains the definitions of your imported targets (e.g., &lt;code&gt;MyLib::MyLib&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create Version and Config Files&lt;/strong&gt;: Use &lt;code&gt;CMakePackageConfigHelpers&lt;/code&gt; to generate a version file (&lt;code&gt;&amp;lt;name&amp;gt;ConfigVersion.cmake&lt;/code&gt;) and write a config file (&lt;code&gt;&amp;lt;name&amp;gt;Config.cmake&lt;/code&gt;). The config file is the entry point that &lt;code&gt;find_package&lt;/code&gt; looks for.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;This process tells other projects how to use your library by defining an imported target like &lt;code&gt;YourProject::YourLib&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;For example:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# standard steps to build the library&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;find_package&lt;/span&gt;&lt;span&gt;(Bar 2.0 REQUIRED)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;add_library&lt;/span&gt;&lt;span&gt;(Foo ...)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_link_libraries&lt;/span&gt;&lt;span&gt;(Foo PRIVATE Bar::Bar)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 1. Install targets&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt;(TARGETS Foo EXPORT FooTargets&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;LIBRARY DESTINATION lib&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ARCHIVE DESTINATION lib&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;RUNTIME DESTINATION bin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;INCLUDES DESTINATION include)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 2. Install Export Set&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt;(EXPORT FooTargets&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;FILE&lt;/span&gt;&lt;span&gt; FooTargets.cmake&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;NAMESPACE Foo::&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;DESTINATION lib/cmake/Foo)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 3. Create Version file&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;include&lt;/span&gt;&lt;span&gt;(CMakePackageConfigHelpers)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;write_basic_package_version_file(&lt;/span&gt;&lt;span&gt;&quot;FooConfigVersion.cmake&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;VERSION &lt;/span&gt;&lt;span&gt;${Foo_VERSION}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;COMPATIBILITY SameMajorVersion&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt;(FILES &lt;/span&gt;&lt;span&gt;&quot;FooConfig.cmake&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;FooConfigVersion.cmake&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;DESTINATION lib/cmake/Foo)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Create and write config files &lt;code&gt;FooConfig.cmake&lt;/code&gt;:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;include&lt;/span&gt;&lt;span&gt;(CMakeFindDependencyMacro)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;find_dependency(Bar 2.0)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;include&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;$${CMAKE_CURRENT_LIST_DIR}/FooTargets.cmake&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;5. Testing with CTest&lt;a href=&quot;#5-testing-with-ctest&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;CTest is CMake’s testing framework. It acts as a test driver, running test executables and reporting results.&lt;/p&gt;&lt;section&gt;&lt;h3&gt;Basic Setup&lt;a href=&quot;#basic-setup&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Enable Testing&lt;/strong&gt;: Add &lt;code&gt;enable_testing()&lt;/code&gt; to your root &lt;code&gt;CMakeLists.txt&lt;/code&gt;. This command should be called only once in the top-level project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add Tests&lt;/strong&gt;: Use &lt;code&gt;add_test()&lt;/code&gt; to define a test case. A test is typically an executable that returns 0 for success and non-zero for failure.&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# In your tests/CMakeLists.txt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;add_executable&lt;/span&gt;&lt;span&gt;(run_all_tests tests.cpp)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;target_link_libraries&lt;/span&gt;&lt;span&gt;(run_all_tests PRIVATE my_lib GTest::GTest GTest::Main)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Define a test named &quot;MyLib.UnitTests&quot; that runs the &quot;run_all_tests&quot; executable.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;add_test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;NAME&lt;/span&gt;&lt;span&gt; MyLib.UnitTests &lt;/span&gt;&lt;span&gt;COMMAND&lt;/span&gt;&lt;span&gt; run_all_tests)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;TIP&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;It’s a good practice to adopt a naming convention for your tests, like &lt;code&gt;Project.Component.TestType&lt;/code&gt;. This makes filtering much easier.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;section&gt;&lt;h4&gt;Running Tests&lt;a href=&quot;#running-tests&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;You can then run all tests from your build directory:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Run all tests, with 4 parallel jobs and verbose output on failure&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ctest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-j4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--output-on-failure&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;Advanced: Filtering Tests&lt;a href=&quot;#advanced-filtering-tests&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;You can run a subset of your tests using a regular expression with the &lt;code&gt;-R&lt;/code&gt; flag.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Run only the tests whose names start with &quot;MyLib.&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ctest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-R&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;^MyLib&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;.&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;Advanced: Testing for Compile Failure&lt;a href=&quot;#advanced-testing-for-compile-failure&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Sometimes, you want to ensure that certain code &lt;em&gt;fails&lt;/em&gt; to compile (e.g., when testing &lt;code&gt;static_assert&lt;/code&gt;). You can create a test for this.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Create a library that is expected to fail compilation.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# EXCLUDE_FROM_ALL prevents it from being built during a normal build.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;add_library&lt;/span&gt;&lt;span&gt;(foo_fail STATIC EXCLUDE_FROM_ALL foo_fail.cpp)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Add a test that tries to build this specific target.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# The test &quot;passes&quot; if the build command fails.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;add_test&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;NAME&lt;/span&gt;&lt;span&gt; Foo.CompileFail&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;COMMAND&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;${CMAKE_COMMAND}&lt;/span&gt;&lt;span&gt; --build &lt;/span&gt;&lt;span&gt;${CMAKE_BINARY_DIR}&lt;/span&gt;&lt;span&gt; --&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt; foo_fail&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# We can even check for a specific error message in the build output.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# The test passes if the build fails AND the output contains this regex.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set_tests_properties&lt;/span&gt;&lt;span&gt;(Foo.CompileFail PROPERTIES&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PASS_REGULAR_EXPRESSION &lt;/span&gt;&lt;span&gt;&quot;static assert message&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;WILL_FAIL &lt;/span&gt;&lt;span&gt;TRUE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;# Informs CTest that a non-zero return code is expected (success)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;Advanced: Driving CTest with a Script&lt;a href=&quot;#advanced-driving-ctest-with-a-script&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;For complex testing scenarios, especially in CI/CD environments, you can use a CMake script to drive CTest.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;build_and_test.cmake&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# This script automates the configure, build, test, and submit steps.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CTEST_SOURCE_DIRECTORY &lt;/span&gt;&lt;span&gt;&quot;/path/to/source&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CTEST_BINARY_DIRECTORY &lt;/span&gt;&lt;span&gt;&quot;/path/to/build&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CTEST_CMAKE_GENERATOR &lt;/span&gt;&lt;span&gt;&quot;Ninja&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;# Specify the generator&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ctest_start&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Continuous&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;# Start a new CTest run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ctest_configure&lt;/span&gt;&lt;span&gt;()         &lt;/span&gt;&lt;span&gt;# Run cmake&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ctest_build&lt;/span&gt;&lt;span&gt;()             &lt;/span&gt;&lt;span&gt;# Run cmake --build&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ctest_test&lt;/span&gt;&lt;span&gt;()              &lt;/span&gt;&lt;span&gt;# Run ctest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ctest_submit&lt;/span&gt;&lt;span&gt;()            &lt;/span&gt;&lt;span&gt;# Submit results to a dashboard like CDash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;You would run this script from the command line:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ctest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;build_and_test.cmake&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;This keeps CI-specific logic out of your main &lt;code&gt;CMakeLists.txt&lt;/code&gt;.&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;6. Cross-Compiling with Toolchain Files&lt;a href=&quot;#6-cross-compiling-with-toolchain-files&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Cross-compiling is the process of building code on one machine (the &lt;em&gt;host&lt;/em&gt;) that is intended to run on a different machine (the &lt;em&gt;target&lt;/em&gt;), which may have a different architecture (e.g., building for ARM on an x86-64 host).&lt;/p&gt;&lt;p&gt;CMake handles this through &lt;strong&gt;Toolchain Files&lt;/strong&gt;. A toolchain file tells CMake how to find the correct compilers, linkers, and libraries for the target system.&lt;/p&gt;&lt;section&gt;&lt;h3&gt;The Role of the Toolchain File&lt;a href=&quot;#the-role-of-the-toolchain-file&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;You specify the toolchain file when you first configure your project with &lt;code&gt;cmake&lt;/code&gt;.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Configure the project using the my-arm-toolchain.cmake file&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cmake&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-B&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;build&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-DCMAKE_TOOLCHAIN_FILE=/path/to/my-arm-toolchain.cmake&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;WARNING&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;A toolchain file is for describing the &lt;strong&gt;environment&lt;/strong&gt;, not for project logic. Avoid setting project options or variables in it. Its only job is to set up the compilers and search paths.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;section&gt;&lt;h4&gt;Example Toolchain File&lt;a href=&quot;#example-toolchain-file&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Here is an example of a toolchain file for cross-compiling to Windows from a Linux host using MinGW-w64.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;mingw-toolchain.cmake&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# The name of the target operating system&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CMAKE_SYSTEM_NAME&lt;/span&gt;&lt;span&gt; Windows)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Specify the cross-compilers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CMAKE_C_COMPILER   x86_64-w64-mingw32-gcc)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Where to find the target environment&apos;s headers and libraries&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Adjust the search behavior for programs, libraries, and includes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Set an emulator to run target executables on the host system&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CMAKE_CROSSCOMPILING_EMULATOR &lt;/span&gt;&lt;span&gt;&quot;wine64&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;Running Cross-Compiled Tests&lt;a href=&quot;#running-cross-compiled-tests&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;You can’t directly run a target executable on the host machine. The &lt;code&gt;CMAKE_CROSSCOMPILING_EMULATOR&lt;/code&gt; variable (set in the toolchain file) tells CTest how to run the tests. It specifies a program (like &lt;code&gt;wine&lt;/code&gt; for Windows apps on Linux, or &lt;code&gt;qemu-arm&lt;/code&gt; for ARM binaries) that can emulate the target environment.&lt;/p&gt;&lt;p&gt;When you run &lt;code&gt;ctest&lt;/code&gt;, it will automatically prefix test commands with the emulator:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# CTest will effectively run:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wine64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my_test_executable.exe&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;This allows you to run your unit tests on the host machine even when cross-compiling.&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;7. Packaging with CPack&lt;a href=&quot;#7-packaging-with-cpack&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;CPack is CMake’s packaging tool. It can create installers (NSIS, WiX), archives (&lt;code&gt;.zip&lt;/code&gt;, &lt;code&gt;.tar.gz&lt;/code&gt;), and Linux packages (&lt;code&gt;.deb&lt;/code&gt;, &lt;code&gt;.rpm&lt;/code&gt;).&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Include CPack&lt;/strong&gt;: Add &lt;code&gt;include(CPack)&lt;/code&gt; to your root &lt;code&gt;CMakeLists.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set CPack Variables&lt;/strong&gt;: Configure package metadata by setting &lt;code&gt;CPACK_*&lt;/code&gt; variables.&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CPACK_PACKAGE_NAME &lt;/span&gt;&lt;span&gt;&quot;MyAwesomeApp&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CPACK_PACKAGE_VERSION &lt;/span&gt;&lt;span&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(CPACK_GENERATOR &lt;/span&gt;&lt;span&gt;&quot;ZIP;TGZ&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;# Create a .zip and a .tar.gz&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# ... other variables&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Run CPack from your build directory to create the packages:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;cpack&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;8. Static Analysis Integration&lt;a href=&quot;#8-static-analysis-integration&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Integrating static analysis into the build process is a powerful way to enforce code quality and catch bugs early.&lt;/p&gt;&lt;section&gt;&lt;h3&gt;The Philosophy of Handling Warnings&lt;a href=&quot;#the-philosophy-of-handling-warnings&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;A common piece of advice is to “treat warnings as errors,” typically by passing the &lt;code&gt;-Werror&lt;/code&gt; flag to the compiler. However, this approach can be counterproductive and painful.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;-Werror&lt;/code&gt; Causes Pain:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Blocks Progress&lt;/strong&gt;: You cannot enable &lt;code&gt;-Werror&lt;/code&gt; until you have fixed every single existing warning in the codebase.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hinders Upgrades&lt;/strong&gt;: You cannot upgrade your compiler or increase the warning level (e.g., from &lt;code&gt;-Wall&lt;/code&gt; to &lt;code&gt;-Wextra&lt;/code&gt;) until you have fixed all the new warings that appear. This creates a significant barrier to modernization.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Makes Deprecation Difficult&lt;/strong&gt;: You cannot mark your own internal APIs as &lt;code&gt;[[deprecated]]&lt;/code&gt; as long as they are still in use, because this would generate a warning and fail the build. But once they are no longer used, you might as well just remove them.&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;A Better Approach: Treat &lt;em&gt;New&lt;/em&gt; Warnings as Errors&lt;a href=&quot;#a-better-approach-treat-new-warnings-as-errors&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;A more practical and agile strategy is to manage warnings in development cycles:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Allow New Warnings Temporarily&lt;/strong&gt;: At the beginning of a development cycle (e.g., a new sprint), allow new warnings to be introduced. This is the time to upgrade compilers, update dependencies, or enable more aggressive warning flags.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Burn Down Warnings&lt;/strong&gt;: During the cycle, the team’s goal is to fix all existing warnings and drive the count back down to zero.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repeat&lt;/strong&gt;: This iterative process allows for continuous improvement without bringing development to a halt.&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Pull Out All the Stops: Powerful Analysis Tools&lt;a href=&quot;#pull-out-all-the-stops-powerful-analysis-tools&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Modern C++ has a rich ecosystem of static analysis tools that go far beyond compiler warnings. You should integrate them into your build.&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;clang-tidy&lt;/strong&gt;: A Clang-based linter framework to diagnose and fix typical programming errors, style violations, and interface misuse.
&lt;strong&gt;cpplint&lt;/strong&gt;: An automated checker to ensure code adheres to Google’s C++ style guide.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;include-what-you-use (IWYU)&lt;/strong&gt;: A tool for analyzing &lt;code&gt;#include&lt;/code&gt;s to ensure you include exactly what you use, which can improve compile times and code clarity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;clazy&lt;/strong&gt;: A Clang plugin that finds Qt-specific antipatterns and performance issues.&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Modern CMake Integration via Target Properties&lt;a href=&quot;#modern-cmake-integration-via-target-properties&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Modern CMake provides dedicated target properties to run these tools alongside the compiler.&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;LANG&amp;gt;_CLANG_TIDY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;LANG&amp;gt;_CPPLINT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;LANG&amp;gt;_INCLUDE_WHAT_YOU_USE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LINK_WHAT_YOU_USE&lt;/code&gt; (Checks for unused library links)&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Where &lt;code&gt;&amp;lt;LANG&amp;gt;&lt;/code&gt; is &lt;code&gt;C&lt;/code&gt; or &lt;code&gt;CXX&lt;/code&gt;. These properties are initialized by their corresponding &lt;code&gt;CMAKE_...&lt;/code&gt; variables.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Example of enabling clang-tidy and include-what-you-use&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;set_target_properties&lt;/span&gt;&lt;span&gt;(my_app PROPERTIES&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CXX_CLANG_TIDY &lt;/span&gt;&lt;span&gt;&quot;clang-tidy;-checks=-*,readability-*,modernize-*&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CXX_INCLUDE_WHAT_YOU_USE &lt;/span&gt;&lt;span&gt;&quot;include-what-you-use;-Xiwyu;--mapping_file=/iwyu.imp&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;The major advantage of this approach is that diagnostics from these tools are seamlessly integrated into your build output and appear directly in your IDE, just like regular compiler errors and warnings.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Best Practice: Analyzing Header Files&lt;a href=&quot;#best-practice-analyzing-header-files&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;A common pitfall is that header files without an associated source file (.cpp) will not be analyzed.&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;strong&gt;TIP&lt;/strong&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;For each header file, ensure there is an associated source file that &lt;code&gt;#include&lt;/code&gt; it, preferably as the very first line. This source file can even be empty otherwise.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;You can use a simple script to create these missing source files:&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# Create an empty .cpp file for every .h file that doesn&apos;t have one.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;comm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-23&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;(&lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;.h &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cut&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &apos;.&apos; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sort&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;(&lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;.c &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;.cpp &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cut&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &apos;.&apos; &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sort&lt;/span&gt;&lt;span&gt;)`&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;#include &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;$fn&lt;/span&gt;&lt;span&gt;.h&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;$fn&lt;/span&gt;&lt;span&gt;.cpp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;done&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Enabling Analysis from Outside the Project&lt;/p&gt;&lt;p&gt;To keep your &lt;code&gt;CMakeLists.txt&lt;/code&gt; clean, you can enable and configure these tools from the command line during the configure step. This is ideal for CI/CD pipelines.&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Reference&lt;a href=&quot;#reference&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=bsXLMQ6WgIk&quot; target=&quot;_blank&quot;&gt;Effective CMake: a random selectino of best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cmake.org/documentation/&quot; target=&quot;_blank&quot;&gt;CMake Documentation and Community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded></item><item><title>Visual Studio C++ 工程实践指南</title><link>https://bfmhno3.github.io/posts/visual-studio-project-management/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/visual-studio-project-management/</guid><description>详解 Visual Studio 解决方案与项目的组织结构、过滤器与路径配置技巧、常用宏的使用，以及静态库与动态库的引用方法，并介绍了 CMake 的集成支持，帮助开发者构建清晰规范的 C++。</description><pubDate>Tue, 29 Jul 2025 17:50:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;解决方案（Solution）与项目（Project）&lt;a href=&quot;#解决方案solution与项目project&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;一个&lt;strong&gt;解决方案&lt;/strong&gt;是用于组织和管理一个或多个相关联&lt;strong&gt;项目&lt;/strong&gt;的容器。这套结构旨在将一个复杂的实际问题，分解成多个功能独立的&lt;strong&gt;模块&lt;/strong&gt;（&lt;strong&gt;项目&lt;/strong&gt;）来开发和管理。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;解决方案&lt;/strong&gt;（&lt;code&gt;.sln&lt;/code&gt; 文件）：这是最高层级的组织单位，它定义了所包含的项目以及它们之间的依赖关系和构建配置。当你打开一个 &lt;code&gt;.sln&lt;/code&gt; 文件时，Visual Studio 会加载整个工作区。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;项目&lt;/strong&gt;（&lt;code&gt;.vcxproj&lt;/code&gt;、&lt;code&gt;csproj&lt;/code&gt; 等文件）：每个项目对应一个独立的模块，最终会生成一个可执行文件（&lt;code&gt;.exe&lt;/code&gt;）、一个静态库（&lt;code&gt;.lib&lt;/code&gt;）或一个动态链接库（&lt;code&gt;.dll&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;需要注意的地方：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单一启动项目&lt;/strong&gt;：在一个解决方案中，通常只有一个项目被设置为&lt;strong&gt;启动项目&lt;/strong&gt;。&lt;strong&gt;启动项目&lt;/strong&gt;是点击 “开始调试”（&lt;kbd&gt;F5&lt;/kbd&gt;）时，Visual Studio 将会编译和运行的项目。可以在&lt;strong&gt;解决方案资源管理器&lt;/strong&gt;中右键点击不同的项目，选择 “&lt;strong&gt;设为启动项目&lt;/strong&gt;”。&lt;/li&gt;
&lt;li&gt;“&lt;strong&gt;将解决方案和项目放在同一目录中&lt;/strong&gt;”：
&lt;ul&gt;
&lt;li&gt;勾选：解决方案文件（&lt;code&gt;.sln&lt;/code&gt;）和项目文件（例如 &lt;code&gt;.vcxproj&lt;/code&gt;）会存放在同一个文件夹中。这对于只有一个或少数几个项目的简单解决方案来说，目录结构更扁平、更清晰。&lt;/li&gt;
&lt;li&gt;不勾选（默认）：Visual Studio 会创建一个顶层文件夹来存放解决方案文件（&lt;code&gt;.sln&lt;/code&gt;），然后在该文件夹下为每一个项目再创建一个独立的子文件夹。当解决方案包含多个项目时，这种结构更加有条理，是&lt;strong&gt;更推荐的做法&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;过滤器（Filter）&lt;a href=&quot;#过滤器filter&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;过滤器&lt;/strong&gt;（&lt;em&gt;Filter&lt;/em&gt;）是 Visual Studio 解决方案资源管理器中的一个&lt;strong&gt;虚拟文件夹结构&lt;/strong&gt;。它的主要作用是帮助开发者组织和分类源文件、头文件等，使项目看起来更整洁，所有的代码文件（源文件和头文件）都应该加入过滤器中。开发者可以自由地创建、重命名或删除过滤器，这&lt;strong&gt;不会&lt;/strong&gt;影响磁盘上文件的实际位置。&lt;/p&gt;&lt;p&gt;而对于编译器（例如 MSVC、GCC 等）在编译代码时，只关心真实的目录结构，而非&lt;strong&gt;过滤器&lt;/strong&gt;中的配置。所以当编译器报错 “无法打开源文件” 或 “找不到头文件” 之类的错误时，问题几乎总是出在&lt;strong&gt;项目属性&lt;/strong&gt;的路径配置上，而不是解决方案资源管理器的过滤器结构。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;如何正确配置包含目录？&lt;a href=&quot;#如何正确配置包含目录&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;当项目需要引入外部头文件（例如来自另一个项目货第三方库）时，必须明确告知编译器去哪里查找这些文件。&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;在&lt;strong&gt;解决方案资源管理器&lt;/strong&gt;中右键点击项目，选择&lt;strong&gt;属性&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;确保顶部的&lt;strong&gt;配置&lt;/strong&gt;和&lt;strong&gt;平台&lt;/strong&gt;设置正确。&lt;/li&gt;
&lt;li&gt;导航到 &lt;strong&gt;C/C++&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;常规&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;在&lt;strong&gt;附加包含目录&lt;/strong&gt;字段中，添加所需头文件所在的&lt;strong&gt;真实目录路径&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;在 Visual Studio 中选择的目录会被设置为&lt;strong&gt;绝对路径&lt;/strong&gt;，当&lt;strong&gt;解决方案&lt;/strong&gt;发生移动，或者发送给合作者时，就会导致错误。这可以通过 Visual Studio 中提供的&lt;strong&gt;宏&lt;/strong&gt;来解决。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;宏（Macro）&lt;a href=&quot;#宏macro&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Visual Studio 提供了一系列预定义的&lt;strong&gt;宏&lt;/strong&gt;（&lt;em&gt;Macro&lt;/em&gt;），这些宏会在构建时被动态地解析为具体的路径。&lt;/p&gt;&lt;p&gt;常用宏变量：&lt;/p&gt;
































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;宏&lt;/th&gt;&lt;th&gt;描述&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$(SolutionDir)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;解决方案文件（&lt;code&gt;.sln&lt;/code&gt;）所在的目录&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$(ProjectDir)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当前项目文件（&lt;code&gt;.vcxproj&lt;/code&gt;）所在的目录&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$(OutDir)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;最终输出文件（如 &lt;code&gt;.exe&lt;/code&gt;、&lt;code&gt;.dll&lt;/code&gt;）的存放目录&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$(IntDir)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;编译过程中产生的中间文件（如 &lt;code&gt;.obj&lt;/code&gt;）的存放目录&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$(Configuration)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当前的构建配置名称，如 &lt;code&gt;Debug&lt;/code&gt; 或 &lt;code&gt;Release&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;$(Platform)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当前的构建平台名称，如 &lt;code&gt;x64&lt;/code&gt; 或 &lt;code&gt;Win32&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;section&gt;&lt;h3&gt;示例&lt;a href=&quot;#示例&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;假设解决方案目录结构如下：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;MySolution/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MySolution.sln&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;├──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProjectA/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;│&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;└──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProjectA.vcxproj&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Libraries/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;└──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyLib/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;└──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;include/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;└──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my_header.h&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如果 &lt;code&gt;ProjectA&lt;/code&gt; 需要包含 &lt;code&gt;my_header.h&lt;/code&gt;，则可以在 &lt;code&gt;ProjectA&lt;/code&gt; 的 “附加包含目录” 中设置为：&lt;code&gt;$(SolutionDir)\Libraries\MyLib\include&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;这样一来，即使将整个 &lt;code&gt;MySolution&lt;/code&gt; 文件夹移动到其他位置，这个相对路径依然有效。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;库的使用&lt;a href=&quot;#库的使用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;静态库（Static Library，&lt;code&gt;.lib&lt;/code&gt;）&lt;a href=&quot;#静态库static-librarylib&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;静态库是在&lt;strong&gt;链接阶段&lt;/strong&gt;被并入到目标程序中的。最终生成的可执行文件会包含静态库的所有代码，因此文件体积较大，但运行时不依赖外部文件。&lt;/p&gt;&lt;section&gt;&lt;h4&gt;创建&lt;a href=&quot;#创建&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;在解决方案中右键点击 “解决方案” &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 添加 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 新建项目 &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 静态库&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h4&gt;使用&lt;a href=&quot;#使用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;&lt;section&gt;&lt;h5&gt;引用&lt;a href=&quot;#引用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;&lt;ul&gt;
&lt;li&gt;在需要使用此库的项目上，右键点击&lt;strong&gt;引用&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;选择&lt;strong&gt;添加引用&lt;/strong&gt;，然后在弹出的对话框中勾选所需的静态库项目&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这是推荐的方式，原因如下：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;它会自动设置&lt;strong&gt;链接器&lt;/strong&gt;的输入，确保链接器能够找到库文件&lt;/li&gt;
&lt;li&gt;它会自动创建&lt;strong&gt;项目依赖项&lt;/strong&gt;，保证在编译主项目之前，静态库项目总是被优先编译&lt;/li&gt;
&lt;li&gt;它还会自动将静态库的头文件目录添加到主项目的&lt;strong&gt;附加包含目录&lt;/strong&gt;中，让开发者能够直接 &lt;code&gt;#include&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h5&gt;手动配置（不推荐）&lt;a href=&quot;#手动配置不推荐&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;&lt;p&gt;如果引用的库是一个已经编译好、非解决方案内的 &lt;code&gt;.lib&lt;/code&gt; 文件，则需要手动配置：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;附加库目录&lt;/strong&gt;：在项目属性的&lt;strong&gt;链接器&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;常规&lt;/strong&gt;中，添加 &lt;code&gt;.lib&lt;/code&gt; 文件所在的目录。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;附加依赖项&lt;/strong&gt;：在项目属性的&lt;strong&gt;链接器&lt;/strong&gt; &lt;span&gt;&lt;span&gt;→\rightarrow&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;→&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;strong&gt;输入&lt;/strong&gt;中，添加 &lt;code&gt;.lib&lt;/code&gt; 文件的具体名称（例如 &lt;code&gt;libtest.lib&lt;/code&gt;）&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;/section&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;动态链接库（Dynamic Link Library，&lt;code&gt;.dll&lt;/code&gt;）&lt;a href=&quot;#动态链接库dynamic-link-librarydll&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;动态库是程序&lt;strong&gt;运行时&lt;/strong&gt;由操作系统加载到内存中的。多个程序可以共享同一个 DLL，从而节省磁盘空间和内存。项目生成时会同时产生一个 &lt;code&gt;.dll&lt;/code&gt; 文件（运行时需要）和一个 &lt;code&gt;.lib&lt;/code&gt; 文件（链接时需要）。&lt;/p&gt;&lt;p&gt;注意事项：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;导出与导入：从 DLL 中暴露函数或类给其他项目使用，需要使用 &lt;code&gt;__declspec(dllexport)&lt;/code&gt; 关键字。而在使用该 DLL 的项目中，则需要使用 &lt;code&gt;__declspc(dllimport)&lt;/code&gt; 来声明这些函数或类。&lt;/li&gt;
&lt;li&gt;运行时环境：当运行依赖于 DLL 的程序时，操作系统必须能够找到这个 &lt;code&gt;.dll&lt;/code&gt; 文件。通常由以下几种方式：
&lt;ol&gt;
&lt;li&gt;将 &lt;code&gt;.dll&lt;/code&gt; 文件放置在与可执行文件（&lt;code&gt;.exe&lt;/code&gt;）相同的目录下&lt;/li&gt;
&lt;li&gt;将 &lt;code&gt;.dll&lt;/code&gt; 文件所在的目录添加到系统的 &lt;code&gt;PATH&lt;/code&gt; 环境变量中。&lt;/li&gt;
&lt;li&gt;在 Visual Studio 中进行调试时，它通常会自动处理好路径问题。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;使用 CMake 自动生成解决方案&lt;a href=&quot;#使用-cmake-自动生成解决方案&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;对于跨平台项目或大型复杂项目，直接使用 Visual Studio 的项目文件进行管理可能会变得非常繁琐。CMake 是一个强大的、开源的构建系统生成工具，它可以根据简单的配置文件（&lt;code&gt;CMakeLists.txt&lt;/code&gt;）自动生成适用与多种平台和 IDE 的原生构建环境，包括 Visual Studio 的 &lt;code&gt;.sln&lt;/code&gt; 和 &lt;code&gt;.vcxproj&lt;/code&gt; 文件。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;手动配置&lt;a href=&quot;#手动配置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;为项目的根目录以及各个子模块（库、可执行文件）编写 &lt;code&gt;CMakeLists.txt&lt;/code&gt; 文件。&lt;/li&gt;
&lt;li&gt;创建一个独立的构建目录（通常命名为 &lt;code&gt;build&lt;/code&gt;），以保持源码树的干净。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;build&lt;/code&gt; 目录中运行 CMake，指令通常为 &lt;code&gt;cmake ..&lt;/code&gt;，它会查找上一级目录的 &lt;code&gt;CMakeLists.txt&lt;/code&gt; 并生成构建文件。&lt;/li&gt;
&lt;li&gt;最后，用 Visual Studio 打开在 &lt;code&gt;build&lt;/code&gt; 中生成的 &lt;code&gt;.sln&lt;/code&gt; 文件。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;原生支持&lt;a href=&quot;#原生支持&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;从 Visual Studio 2017 开始，Visual Studio 提供了对 CMake 的原生集成支持。&lt;/p&gt;&lt;p&gt;可以直接用 Visual Studio 打开一个包含 &lt;code&gt;CMakeLists.txt&lt;/code&gt; 的文件节哀，而无需手动运行 &lt;code&gt;cmake&lt;/code&gt; 命令和打开生成的 &lt;code&gt;.sln&lt;/code&gt; 文件。Visual Studio 会自动在后台完成这一切，并提供完整的智能提示、构建和调试支持。&lt;/p&gt;&lt;p&gt;这对于使用 CMake 的项目来说，是目前最便捷的开发方式。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1S541137P2?spm_id_from=333.788.videopod.episodes&amp;amp;vd_source=aece08350e40b390e044f67307ffd650&quot; target=&quot;_blank&quot;&gt;工程课代表——Visual Studio 工程实践开发&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;</content:encoded></item><item><title>VMware Workstation 17.x 中的虚拟机按键反应迟钝解决办法</title><link>https://bfmhno3.github.io/posts/vmware-keyboard-lag-fix/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/vmware-keyboard-lag-fix/</guid><description>本文介绍了如何解决 VMware Workstation 17.x 中虚拟机（如 Ubuntu）按键反应迟钝、跟手性差的问题。通过在 .vmx 配置文件中添加 keyboard.vusb.enable = &quot;TRUE&quot; 参数，可以有效改善输入延迟，无需降低 CPU 核心数或关闭 3D 图形加速。</description><pubDate>Fri, 23 May 2025 14:34:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;问题描述&lt;a href=&quot;#问题描述&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在 VMware Workstation 17.x 中的虚拟机按键反映迟钝，具体而言即是屏幕显示的字符速度跟不上手打字的速度，你能很明显得感觉到迟钝。&lt;/p&gt;&lt;p&gt;特别是长按方向键选择时，长按一两秒，光标会移动好几秒，跟手性非常差，非常难受。&lt;/p&gt;&lt;p&gt;笔者这里所说的“&lt;strong&gt;跟手性&lt;/strong&gt;”是指：当用户开始输入时，屏幕就开始刷新字符，刷新字符的速度与用户打字的速度不能差太多，并且当用户停止输入时，屏幕能立即停止刷新字符。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;笔者环境&lt;a href=&quot;#笔者环境&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;VMware Workstatio 17 Pro：17.6.3 build-24583834&lt;/li&gt;
&lt;li&gt;虚拟机：Ubuntu 16.04&lt;/li&gt;
&lt;li&gt;操作系统：Windows 11 专业版 26100.4061&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;解决方法&lt;a href=&quot;#解决方法&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;请按照以下步骤操作，修改虚拟机的配置文件：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;关闭虚拟机&lt;/strong&gt;：确保虚拟机处于完全关闭状态（Power Off），而非挂起（Suspend）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;找到配置文件&lt;/strong&gt;：进入虚拟机所在的文件夹，找到后缀为 &lt;code&gt;.vmx&lt;/code&gt; 的配置文件（通常与虚拟机同名）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编辑文件&lt;/strong&gt;：右键点击该文件，选择使用记事本（Notepad）或 VS Code 打开。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;添加配置&lt;/strong&gt;：在文件的末尾添加以下代码：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;keyboard.vusb.enable&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;TRUE&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;保存并重启&lt;/strong&gt;：保存文件后，重新启动虚拟机即可生效。&lt;/li&gt;
&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;原理说明&lt;/strong&gt;：该配置强制启用了虚拟 USB 键盘设备，绕过了某些导致延迟的输入仿真层。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;测试环境&lt;a href=&quot;#测试环境&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Ubuntu 16.04&lt;/li&gt;
&lt;li&gt;Ubuntu 18.04&lt;/li&gt;
&lt;li&gt;Ubuntu 22.04&lt;/li&gt;
&lt;li&gt;ElementaryOS 7 Horus&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;在以上虚拟机中，笔者对此方法进行了测试，都取得了很好的效果。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;FAQ&lt;a href=&quot;#faq&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;是否需要降低核心数&lt;a href=&quot;#是否需要降低核心数&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;网上有很多其他的教程说可以降低 CPU 的核心数，具体原理是减少虚拟机等待的时间。&lt;/p&gt;&lt;p&gt;笔者实测，降低 CPU 核心数确实能带来一定速度的提升，但是效果很微弱。&lt;/p&gt;&lt;p&gt;但是当我在 &lt;code&gt;*.vmx&lt;/code&gt; 文件中添加了 &lt;code&gt;keyboard.vusb.enable = &quot;TRUE&quot;&lt;/code&gt; 后，我将虚拟机的核心数提高到了 16 个，在虚拟机中打字时的跟手性也非常好，只是整个虚拟机有时候会有点卡顿，完全在可接受的范围内。&lt;/p&gt;&lt;p&gt;不过一般来说，核心数只需要设置为 1-4 即可，不然可能会拖慢宿主机的运行。&lt;/p&gt;&lt;p&gt;终上所述，核心数并无太大影响，读者自己有数就行。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;是否需要关闭 3D 图形加速&lt;a href=&quot;#是否需要关闭-3d-图形加速&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;网上还有很多的教程说需要关闭 3D 图形加速，但是有很多软件的运行是需要这个功能的比如 Gazebo，不然就会变得非常卡。&lt;/p&gt;&lt;p&gt;关闭 3D 图形加速后，确实能够比较明显的感觉到在虚拟机中打字时的跟手性有改善，但是也还没有到那种可以忍受的地步（至少对我而言）。&lt;/p&gt;&lt;p&gt;在 &lt;code&gt;*.vmx&lt;/code&gt; 文件中添加了 &lt;code&gt;keyboard.vusb.enable = &quot;TRUE&quot;&lt;/code&gt; 后，我开启了 3D 图形加速，笔者实测打字的跟手性依旧非常好，同上的效果，只是虚拟机本身有一点卡顿，但完全在可接受的范围内。&lt;/p&gt;&lt;p&gt;综上所述，无需关闭 3D 图形加速。不过开启了 3D 加速确实会影响虚拟机的运行，如果你的工作不需要大量 3D 渲染的话，笔者还是建议关闭 3D 图形加速功能。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://community.broadcom.com/vmware-cloud-foundation/discussion/ws-1761-keyboard-lag-with-ubuntu-guest&quot; target=&quot;_blank&quot;&gt;WS 17.6.1 keyboard lag with Ubuntu guest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/vmware/comments/15kx569/experiencing_keyboard_laginput_delay_on_any_linux/&quot; target=&quot;_blank&quot;&gt;Experiencing keyboard lag/input delay on any Linux Distribution&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded></item><item><title>学习 fgets 函数和 strcspn 函数</title><link>https://bfmhno3.github.io/posts/learning-fgets-and-strcspn/</link><guid isPermaLink="true">https://bfmhno3.github.io/posts/learning-fgets-and-strcspn/</guid><description>本文记录了在 C 语言刷题过程中遇到的字符串输入问题。详细介绍了 fgets 函数和 strcspn 函数的用法，用于解决 scanf 无法读取带空格字符串以及换行符处理的问题，并补充了 scanf(&quot;%[^\n]&quot;) 这一更简洁的替代方案。</description><pubDate>Sun, 20 Aug 2023 15:44:31 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;0. 为什么要专门发布一篇文章来说明这两个函数？&lt;a href=&quot;#0-为什么要专门发布一篇文章来说明这两个函数&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;原因是今晚上在做牛客网的题目的时候，遇到了这么一个&lt;a href=&quot;https://www.nowcoder.com/questionTerminal/cc57022cb4194697ac30bcb566aeb47b?f=discussion&amp;amp;toCommentId=16721626&quot; target=&quot;_blank&quot;&gt;问题&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;虽然这道题对大多数人来说都算是比较简单的题目，但是对于我这么一个小白还是比较难写的。最开始我是这么写的代码：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reverse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;char*&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;char*&lt;/span&gt;&lt;span&gt; left &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; arr;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;char*&lt;/span&gt;&lt;span&gt; right &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;strlen&lt;/span&gt;&lt;span&gt;(arr) &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt;(left &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; right) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; mid &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;left;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;left &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;right;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;right &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; mid;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;strlen&lt;/span&gt;&lt;span&gt;(arr); i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;%c&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;[i]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;10000&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;span&gt;//之所以创建这么大一个数组是因为题目中的示例10非常长&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;scanf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;%s&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, arr);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;reverse&lt;/span&gt;&lt;span&gt;(arr);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;但是这样写代码连题目中的示例 1：输入：&lt;code&gt;I am a student&lt;/code&gt;；输出：&lt;code&gt;tneduts a ma I&lt;/code&gt; 都没有通过，最终只输出了一个 &lt;code&gt;I&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;这是为什么呢？我思索了半天才明白是我的这一行代码 &lt;code&gt;scanf(&quot;%s&quot;, arr);&lt;/code&gt; 有问题，就是因为 &lt;code&gt;scanf(&quot;%s&quot;, arr)&lt;/code&gt; 会读取一个字符串，但会在遇到空格、制表符或换行符时停止。换句话说，&lt;strong&gt;它只会读取输入字符串中的第一个单词，然后停止&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;例如，如果你输入 “Hello World”，&lt;code&gt;scanf(&quot;%s&quot;, str)&lt;/code&gt; 会将 “Hello” 作为一个字符串读取，而 “World” 则会留在输入流中等待后续读取。&lt;/p&gt;&lt;p&gt;所以示例输入的 &lt;code&gt;I am a student&lt;/code&gt; 只有 &lt;code&gt;I&lt;/code&gt; 进入了 &lt;code&gt;arr[10000]&lt;/code&gt; 中，剩下的字符串仍然留在输入流中等待下一次读取。所以最终自然而然也就只会输出 &lt;code&gt;I&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;知道了问题的原因自然就要想办法解决问题，但是说实话，对于小编这种很笨的学生来说，几乎是不可能的。所以，我就去求助了 ChatGPT。ChatGPT 给出的解决办法是使用 &lt;code&gt;fgets&lt;/code&gt; 函数，但是我真就第一次听这个函数，也完全不懂这个函数该如何使用，于是我就准备写下这篇文章来记录我的欠缺。&lt;/p&gt;&lt;p&gt;那么下面就开始正题：&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;1. &lt;code&gt;fgets&lt;/code&gt;函数&lt;a href=&quot;#1-fgets函数&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;1.1 函数介绍&lt;a href=&quot;#11-函数介绍&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;fgets&lt;/code&gt; 是 C 语言标准库 &lt;code&gt;stdio&lt;/code&gt; 中的一个函数，用于从文件流（通常是标准输入流 &lt;code&gt;stdin&lt;/code&gt; 或其他文件流）中读取一行字符串。它的主要作用是读取指定长度的字符，并且&lt;strong&gt;会保留换行符（如果存在的话）&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;函数原型：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;fgets&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; FILE &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt;stream&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;1.2 参数说明&lt;a href=&quot;#12-参数说明&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;str&lt;/code&gt;：一个指向字符数组的指针，用于存储读取的字符串。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;n&lt;/code&gt;：要读取的最大字符数（包括字符串结尾的空字符 \0），通常为字符数组的长度。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stream&lt;/code&gt;：文件流指针，通常是 &lt;code&gt;stdin&lt;/code&gt;（标准输入流）或其他文件指针。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fgets&lt;/code&gt; 会读取指定长度的字符，直到遇到换行符（&lt;strong&gt;包括换行符在内&lt;/strong&gt;），或者读取了 &lt;code&gt;n-1&lt;/code&gt; 个字符（包括字符串结尾的空字符 &lt;code&gt;\0&lt;/code&gt; ），或者到达文件末尾为止。它会将读取的字符串存储到 &lt;code&gt;str&lt;/code&gt; 指向的字符数组中，并在字符串的末尾添加一个空字符 &lt;code&gt;\0&lt;/code&gt;，以表示字符串的结束。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;1.3 注意事项&lt;a href=&quot;#13-注意事项&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fgets&lt;/code&gt; 会保留字符串中的换行符，所以如果不需要换行符，可以使用字符串处理函数（如 &lt;code&gt;strcspn&lt;/code&gt;）将其替换为空字符 &lt;code&gt;\0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果输入的字符串的长度超过了指定的 &lt;code&gt;n-1&lt;/code&gt;，&lt;code&gt;fgets&lt;/code&gt; 只会读取 &lt;code&gt;n-1&lt;/code&gt; 个字符，其余字符会留在输入流中，可能影响后续的输入。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fgets&lt;/code&gt; 在成功读取字符串时返回 &lt;code&gt;str&lt;/code&gt;，在到达文件末尾或发生错误时返回 &lt;code&gt;NULL&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;1.4 使用示例&lt;a href=&quot;#14-使用示例&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;span&gt; // 最多读取 99 个字符（包括结尾的空字符）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;请输入一个字符串：&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;fgets&lt;/span&gt;&lt;span&gt;(str, &lt;/span&gt;&lt;span&gt;sizeof&lt;/span&gt;&lt;span&gt;(str), stdin);&lt;/span&gt;&lt;span&gt; // 从标准输入读取一行字符串&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;strcspn&lt;/span&gt;&lt;span&gt;(str, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\0&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; // 去掉换行符（如果有）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;你输入的字符串是：&lt;/span&gt;&lt;span&gt;%s&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, str);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;1.5 题目代码修改&lt;a href=&quot;#15-题目代码修改&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;所以我原本写的代码中的 &lt;code&gt;scanf(&quot;%s&quot;, arr)&lt;/code&gt; 应该改成 &lt;code&gt;fgets(arr, sizeof(arr), stdin)&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;是否这么写代码我就能通过测试了呢？&lt;a href=&quot;#是否这么写代码我就能通过测试了呢&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;当然不是，事实上代码最终的输出结果是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;tneduts a ma I&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;所以最后的输出还是错误了，多输出了一个换行符 &lt;code&gt;\n&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;事实上这里有很多种方法可以去除原来字符串末尾的换行符，但我最终选择了使用 &lt;code&gt;strcspn&lt;/code&gt; 函数来处理,所以下面我会介绍 &lt;code&gt;strcspn&lt;/code&gt; 函数。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2. &lt;code&gt;strcspn&lt;/code&gt;函数&lt;a href=&quot;#2-strcspn函数&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;2.1 函数介绍&lt;a href=&quot;#21-函数介绍&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;strcspn&lt;/code&gt; 是 C 语言标准库 &lt;code&gt;string&lt;/code&gt; 中的一个函数，用于查找一个字符串中第一次出现指定字符集合中的任何字符的位置。&lt;strong&gt;它返回的是第一个匹配字符的索引位置&lt;/strong&gt;。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;size_t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;strcspn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt;str1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt;str2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.2 参数说明&lt;a href=&quot;#22-参数说明&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;str1&lt;/code&gt;：要查找的字符串。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;str2&lt;/code&gt;：一个包含字符集合的字符串，&lt;code&gt;strcspn&lt;/code&gt; 会查找这个字符集合中的字符在 &lt;code&gt;str1&lt;/code&gt; 中的第一次出现。&lt;/li&gt;
&lt;li&gt;strcspn 会从 &lt;code&gt;str1&lt;/code&gt; 的起始位置开始查找，直到遇到 &lt;code&gt;str2&lt;/code&gt; 中的任何字符，或者到达字符串末尾。&lt;strong&gt;它返回第一个匹配字符的索引位置，如果未找到匹配字符，则返回 &lt;code&gt;str1&lt;/code&gt; 的长度&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.3 注意事项&lt;a href=&quot;#23-注意事项&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;strcspn&lt;/code&gt; 返回的是 &lt;code&gt;size_t&lt;/code&gt; 类型，它是无符号整数类型。如果未找到匹配字符，返回值会等于字符串的长度。因此，在使用返回值时，应该&lt;strong&gt;注意数据类型和范围&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2.4 使用示例&lt;a href=&quot;#24-使用示例&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; str&lt;/span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Hello, World!&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; chars&lt;/span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot; ,&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; // 字符集合包含空格和逗号&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;size_t&lt;/span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;strcspn&lt;/span&gt;&lt;span&gt;(str, chars);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (index &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;strlen&lt;/span&gt;&lt;span&gt;(str)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;第一个匹配字符在位置：&lt;/span&gt;&lt;span&gt;%zu&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, index);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;匹配字符是：&lt;/span&gt;&lt;span&gt;%c&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;[index]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;未找到匹配字符&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;3. 最终代码&lt;a href=&quot;#3-最终代码&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;reverse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;char*&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;char*&lt;/span&gt;&lt;span&gt; left &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; arr;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;char*&lt;/span&gt;&lt;span&gt; right &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;strlen&lt;/span&gt;&lt;span&gt;(arr) &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt;(left &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; right) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; mid &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;left;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;left &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;right;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;right &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; mid;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;strlen&lt;/span&gt;&lt;span&gt;(arr); i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;printf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;%c&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;[i]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;100000&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;fgets&lt;/span&gt;&lt;span&gt;(arr, &lt;/span&gt;&lt;span&gt;sizeof&lt;/span&gt;&lt;span&gt;(arr), stdin);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;arr&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;strcspn&lt;/span&gt;&lt;span&gt;(arr, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\0&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;reverse&lt;/span&gt;&lt;span&gt;(arr);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;4. 题后反思&lt;a href=&quot;#4-题后反思&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;最终的代码运行成功后，我总有一种大材小用的感觉，我就思考能不能用 &lt;code&gt;scanf&lt;/code&gt; 函数来实现呢？经过与 ChatGPT 的一番讨论后，我最终找到了另一种更为简单的代码：那就是使用 &lt;code&gt;%[^\n]&lt;/code&gt; 来格式化输入。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;5. &lt;code&gt;%[^\n]&lt;/code&gt;&lt;a href=&quot;#5-n&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;5.1 介绍与解释&lt;a href=&quot;#51-介绍与解释&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;%[^\n]&lt;/code&gt; 是 C 语言中用于格式化输入的格式字符串之一。它用于 &lt;code&gt;scanf&lt;/code&gt; 函数中，用于读取一行字符串，直到遇到换行符为止。&lt;/p&gt;&lt;p&gt;让我解释一下 &lt;code&gt;%[^\n]&lt;/code&gt; 的含义：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;%[&lt;/code&gt;：这部分表示开始读取一个自定义字符集。在方括号内放置你想要读取的字符的集合。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;^&lt;/code&gt;：在方括号内的开头，&lt;code&gt;^&lt;/code&gt; 表示反转字符集。即，&lt;code&gt;%[^\n]&lt;/code&gt; 表示读取除换行符外的所有字符。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\n&lt;/code&gt;：这是一个转义序列，表示换行符。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以，&lt;code&gt;%[^\n]&lt;/code&gt; 的意思是，&lt;strong&gt;读取除换行符之外的所有字符，直到遇到换行符为止&lt;/strong&gt;，并且&lt;strong&gt;不会读取换行符&lt;/strong&gt;！！！&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;5.2 再次修改代码&lt;a href=&quot;#52-再次修改代码&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;所以，我们最终可以将代码从 &lt;code&gt;fgets(arr, sizeof(arr), stdin); arr[strcspn(arr, &quot;\n&quot;)];&lt;/code&gt; 修改成 &lt;code&gt;scanf(&quot;%[^\n]&quot;);&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;6. 感悟&lt;a href=&quot;#6-感悟&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;通过这到题目，我深刻的感受到了什么叫做“条条大路通罗马”。作为小白的我，还有很多的知识去学习、去探索、去使用。&lt;/p&gt;&lt;/section&gt;</content:encoded></item></channel></rss>