<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>第 6 章 函数、延迟与恐慌 on Go 语言原本</title>
    <link>/zh-cn/part2lang/ch06func/</link>
    <description>Recent content in 第 6 章 函数、延迟与恐慌 on Go 语言原本</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <atom:link href="/zh-cn/part2lang/ch06func/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>6.1 函数调用</title>
      <link>/zh-cn/part2lang/ch06func/func/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch06func/func/</guid>
      <description>&lt;h1 id=&#34;61-函数调用&#34;&gt;6.1 函数调用&lt;/h1&gt;&#xA;&lt;p&gt;函数是 Go 的一等公民，可以赋值、传递、返回、捕获外部变量成为闭包。这背后是两件事的实现：&#xA;函数值（闭包）在内存里如何表示，以及一次函数调用在底层如何传参与返回。后者还藏着 Go 1.17&#xA;一次悄无声息却影响全局的变革：从栈传参改为寄存器传参。这一节把这两件事讲透，并在它们的接缝处&#xA;点出一个关键事实，正是这个事实把「闭包是什么」与「调用怎么发生」焊成同一件事。&lt;/p&gt;&#xA;&lt;h2 id=&#34;611-函数值与闭包&#34;&gt;6.1.1 函数值与闭包&lt;/h2&gt;&#xA;&lt;p&gt;一个 &lt;code&gt;func&lt;/code&gt; 类型的值，本质是一个指向&lt;strong&gt;闭包对象&lt;/strong&gt;的指针。运行时给这个对象起的名字是 &lt;code&gt;funcval&lt;/code&gt;，&#xA;它在 &lt;code&gt;runtime/runtime2.go&lt;/code&gt; 里的定义短得出奇：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// funcval：函数值在运行时的表示（来自 runtime2.go，已加注释）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;funcval&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fn&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 后面紧跟变长的、与具体函数相关的数据，即被捕获的环境&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;只有一个固定字段 &lt;code&gt;fn&lt;/code&gt;,函数代码的入口地址。注释里那句「variable-size, fn-specific data here」&#xA;才是闭包的灵魂：紧随 &lt;code&gt;fn&lt;/code&gt; 之后，编译器排布着这个闭包捕获的外部变量。一个不捕获任何变量的&#xA;普通函数，它的 &lt;code&gt;funcval&lt;/code&gt; 只有 &lt;code&gt;fn&lt;/code&gt; 一字，全局唯一、只读；一旦它捕获了外部变量，编译器就要为每次&#xA;求值在堆上构造一个带捕获数据的 &lt;code&gt;funcval&lt;/code&gt;。可以把它画成这样：&lt;/p&gt;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart LR&#xA;    FV[&amp;#34;func 值&amp;lt;br/&amp;gt;（一个指针）&amp;#34;] --&amp;gt; CO&#xA;    subgraph CO[&amp;#34;闭包对象 funcval&amp;#34;]&#xA;        direction TB&#xA;        FN[&amp;#34;fn：函数代码入口地址&amp;#34;]&#xA;        CAP[&amp;#34;captured：被捕获的变量&amp;lt;br/&amp;gt;（如 &amp;amp;x, &amp;amp;y，按引用）&amp;#34;]&#xA;        FN --- CAP&#xA;    end&#xA;    CALL[&amp;#34;调用 f()&amp;#34;] -.-&amp;gt;|取 fn 字段间接跳转| FN&lt;/pre&gt;&#xA;&lt;p&gt;调用一个函数值，就是取出 &lt;code&gt;fn&lt;/code&gt; 字段间接跳转过去，与接口的方法分发&#xA;（&lt;a href=&#34;../../ch04type/interface&#34;&gt;4.2&lt;/a&gt;）异曲同工。关键细节是&lt;strong&gt;捕获按引用&lt;/strong&gt;：闭包捕获的是变量本身，&#xA;不是它当时的值。下面这段代码里，&lt;code&gt;counter&lt;/code&gt; 返回的两个闭包共享同一个 &lt;code&gt;n&lt;/code&gt;，因为它们捕获的是&#xA;&lt;code&gt;n&lt;/code&gt; 这个变量的地址，而非某个快照：&lt;/p&gt;</description>
    </item>
    <item>
      <title>6.2 延迟语句</title>
      <link>/zh-cn/part2lang/ch06func/defer/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch06func/defer/</guid>
      <description>&lt;h1 id=&#34;62-延迟语句&#34;&gt;6.2 延迟语句&lt;/h1&gt;&#xA;&lt;p&gt;延迟语句 &lt;code&gt;defer&lt;/code&gt; 在最早期的 Go 设计中并不存在，是后来才单独增补的特性，由 Robert Griesemer&#xA;完成语言规范的编写 [Griesemer, 2009]，Ken Thompson 完成最早期的实现 [Thompson, 2009]。&#xA;它的语义读起来很短：被 &lt;code&gt;defer&lt;/code&gt; 的调用会在外层函数返回、发生 panic、或调用 &lt;code&gt;runtime.Goexit&lt;/code&gt;&#xA;时执行。直觉上这像是一个纯编译期特性，类似 C++ 的 RAII（离开作用域自动析构），编译器似乎只需把&#xA;延迟的调用「搬」到函数末尾即可，不该有运行时开销。&lt;/p&gt;&#xA;&lt;p&gt;真实情况要复杂一层。Go 的 &lt;code&gt;defer&lt;/code&gt; 没有与某个资源的作用域绑定，还允许出现在条件、循环里，于是&#xA;它不再是一个静态的作用域概念。一段执行次数取决于运行时的循环，能产生多少个延迟调用在编译期无法&#xA;确定：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;randomDefers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Intn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;42&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;golang-design/under-the-hood&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 次数运行时才定&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;正因为存在这种不确定性，&lt;code&gt;defer&lt;/code&gt; 不是免费的午餐。本节先讲清它的语义，再沿着一条贯穿十余年的&#xA;性能演进主线，看它如何从「最慢时被劝退出热路径」一路优化到「几乎零成本」。这条主线的终点，也是&#xA;理解 &lt;a href=&#34;.././panic&#34;&gt;6.3 恐慌与恢复&lt;/a&gt; 的钥匙：在今天的运行时里，正常返回时跑 &lt;code&gt;defer&lt;/code&gt; 与 panic 展开&#xA;时跑 &lt;code&gt;defer&lt;/code&gt;，走的是同一套机器。&lt;/p&gt;&#xA;&lt;h2 id=&#34;621-语义lifo返回与-panic-都触发参数在-defer-处求值&#34;&gt;6.2.1 语义：LIFO、返回与 panic 都触发、参数在 defer 处求值&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;defer&lt;/code&gt; 的可观察语义有三条，记住它们就能解释绝大多数「为什么是这个结果」的疑惑。&lt;/p&gt;</description>
    </item>
    <item>
      <title>6.3 恐慌与恢复内建函数</title>
      <link>/zh-cn/part2lang/ch06func/panic/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch06func/panic/</guid>
      <description>&lt;h1 id=&#34;63-恐慌与恢复内建函数&#34;&gt;6.3 恐慌与恢复内建函数&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;defer&lt;/code&gt;（&lt;a href=&#34;.././defer&#34;&gt;6.2&lt;/a&gt;）已经把「函数退出时要做什么」交代清楚，留下的悬念是「函数被&#xA;异常地打断时又会发生什么」。&lt;code&gt;panic&lt;/code&gt; 与 &lt;code&gt;recover&lt;/code&gt; 这对内建函数回答的正是这个问题：前者中断&#xA;当前的正常控制流并开始沿调用栈向上展开，后者在展开途中把它截住。这一节先讲清它们的语义，&#xA;再落到 go1.26 运行时里 &lt;code&gt;gopanic&lt;/code&gt;、&lt;code&gt;gorecover&lt;/code&gt; 的实现，最后回到一个更要紧的问题：panic&#xA;究竟该被当作什么，以及为什么 Go 没有把它做成 C++ 或 Java 那样的异常机制。&lt;/p&gt;&#xA;&lt;h2 id=&#34;631-语义展开拦截与进程终止&#34;&gt;6.3.1 语义：展开、拦截与进程终止&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;panic&lt;/code&gt; 做三件事，顺序固定。它先停下当前函数余下的语句，转而按后进先出的次序执行当前&#xA;goroutine 已经登记的 &lt;code&gt;defer&lt;/code&gt; 链；每执行一个延迟函数，控制权都短暂回到用户代码，给它一次&#xA;拦截的机会；若整条链走完仍无人拦截，panic 会逐层弹出调用栈，到达 goroutine 栈顶后终止&#xA;&lt;strong&gt;整个进程&lt;/strong&gt;，并打印 panic 值与栈回溯。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;recover&lt;/code&gt; 是拦截的唯一手段，但它的生效条件很窄：只有&lt;strong&gt;直接&lt;/strong&gt;被某个延迟函数调用时才有效。&#xA;直接的意思是，调用 &lt;code&gt;recover&lt;/code&gt; 的那一帧，必须正是发生 panic 的那一帧用 &lt;code&gt;defer&lt;/code&gt; 登记的延迟&#xA;函数本身，多一层嵌套或少一层都不算。一次成功的 &lt;code&gt;recover&lt;/code&gt; 会让 panic 停止展开，返回当时&#xA;传给 &lt;code&gt;panic&lt;/code&gt; 的值，随后控制流如同那个延迟函数正常返回一般，继续往下走。&lt;/p&gt;&#xA;&lt;p&gt;这条「同一调用链、且直接位于延迟函数中」的约束，是后文一切实现细节的根。先用三段代码把它&#xA;落实。第一段，&lt;code&gt;recover&lt;/code&gt; 写错了位置，拦不住：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;B&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;C&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;B&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;recover&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 拦不住 C 的 panic：B 已经返回，不在展开路径上&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;C&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;panic&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;boom&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;A&lt;/code&gt; 先调用 &lt;code&gt;B&lt;/code&gt;，&lt;code&gt;B&lt;/code&gt; 正常返回、它的 &lt;code&gt;defer&lt;/code&gt; 也早已执行完毕；随后 &lt;code&gt;A&lt;/code&gt; 调用 &lt;code&gt;C&lt;/code&gt;，&lt;code&gt;C&lt;/code&gt; 发生&#xA;panic 时展开的是 &lt;code&gt;C → A&lt;/code&gt; 这条路径，&lt;code&gt;B&lt;/code&gt; 根本不在其上。第二段，把 &lt;code&gt;recover&lt;/code&gt; 放到调用链上游&#xA;的 &lt;code&gt;A&lt;/code&gt; 里，就拦得住：&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
