defer执行顺序为LIFO(后进先出),但参数在defer语句注册时即完成求值并快照,后续变量修改不影响已注册的defer调用;闭包捕获变量本身而非值,故需显式传参或局部变量避免循环中输出相同值。

defer 的执行顺序是逆序,但参数求值在 defer 注册时就已完成
你写 defer fmt.Println(i),哪怕 i 后续被修改,打印的仍是注册那一刻的值。这是最常被忽略的“求值时机”问题,不是执行顺序问题。
为什么 defer 注册时就求值参数?
Go 在遇到 defer 语句时,会立即对函数名和所有参数做一次完整求值(evaluate),然后把结果“快照”下来,压入当前 goroutine 的 defer 链表。函数体本身不执行,等到外层函数 return 前才按 LIFO 顺序调用。
这意味着:
-
defer f(x)中的x是当时值,不是运行时读取 -
defer f(&x)中的&x是当时地址,后续改x会影响实际输出 - 闭包捕获变量也遵循同一规则:捕获的是变量本身,不是值
defer 链表结构影响 recover 行为
defer 不是栈帧局部结构,而是挂在 goroutine 结构体上的单向链表(_defer 节点)。这直接决定:
-
recover()只在defer函数中调用才有效 —— 因为 panic 流程会遍历该链表,逐个执行,只有正在执行的那个defer函数能拿到 panic value - 如果多个
defer都调用recover(),只有第一个能捕获,后续返回nil - 链表节点分配在堆上(1.22+ 默认),不受栈伸缩影响,但频繁 defer 可能触发 GC
嵌套函数里用 defer 容易踩的坑
常见错误是误以为 defer 会“绑定到闭包环境”,其实它只绑定参数快照和函数指针:
立即学习“go语言免费学习笔记(深入)”;
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
<pre class="brush:php;toolbar:false;">for i := 0; i < 3; i++ {
defer func() { fmt.Println(i) }() // 全部输出 3
}
正确写法是显式传参:
<code>for i := 0; i < 3; i++ {
defer func(x int) { fmt.Println(x) }(i)
}
或者用变量遮蔽:
<code>for i := 0; i < 3; i++ {
i := i
defer func() { fmt.Println(i) }()
}
goroutine 退出时 defer 一定执行吗?
不一定。以下情况 defer 不会执行:
- 调用
os.Exit()—— 进程立刻终止,不走任何 defer / panic / finalizer 流程 - 发生 fatal error(如栈溢出、调度器死锁检测触发)
- 被系统信号强制 kill(SIGKILL)
- runtime.Goexit() 会执行当前 goroutine 的 defer,但不会影响其他 goroutine
注意:panic 后的 defer 仍会执行,这是设计使然;但 os.Exit() 是唯一明确绕过整个 defer 链的合法方式。

















