多个defer倒序执行,因Go将其压入隐式栈,函数退出时从栈顶弹出;参数在注册时求值;可修改命名返回值;panic时仍执行且recover必须在defer内调用。

多个defer为什么是倒序执行
Go 把每个 defer 压进函数内部一个隐式栈,函数退出(包括 return、panic)时统一从栈顶弹出执行。所以写在前面的 defer 反而最后跑,写在后面的最先跑。
常见错误现象:以为 defer f.Close() 写在 defer db.Close() 上面,就会先关文件再关数据库连接——实际顺序相反,可能触发 invalid connection panic。
- 资源释放必须按“后开先关”逆序:比如先
os.Open,再bufio.NewReader,就得先注册defer reader.Close(),再注册defer file.Close() - 锁和 goroutine 配合时尤其危险:如果
mu.Lock()后启了 goroutine,defer mu.Unlock()必须确保 goroutine 已退出,否则可能 race - 这个顺序和代码书写位置无关,只和注册时机有关——把要**先执行**的逻辑,写在**更靠后的 defer 语句里**
defer参数在注册时就求值
defer fmt.Println(i) 中的 i 不是函数退出时的值,而是这行代码执行那一刻的快照。循环里直接这么写,几乎必然出错。
错误写法:
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}输出全是 3,因为循环结束时 i == 3,所有 defer 都读到这个最终值。
- 正确做法一:用立即执行的匿名函数传值
for i := 0; i < 3; i++ { defer func(v int) { fmt.Println(v) }(i) } - 正确做法二:在循环体内重声明变量
for i := 0; i < 3; i++ { i := i // 新变量 defer fmt.Println(i) } - 闭包捕获地址(
defer func(){ fmt.Println(i) }())和直接写defer fmt.Println(i)效果一样,都取最终值,不是延迟求值
defer能修改命名返回值但不能改匿名返回值
return 分两步:先计算并写入返回值(寄存器或栈帧),再执行所有已注册的 defer,最后真正跳出去。这意味着 defer 能读写命名返回值,但对匿名返回值无效。
立即学习“go语言免费学习笔记(深入)”;
示例:
func f() (x int) {
x = 1
defer func() { x++ }()
return
}返回 2;而func f() int {
x := 1
defer func() { x++ }()
return x
}返回 1,因为 return x 已把 1 拷贝出去,defer 改的是局部变量 x。
- 指针返回值例外:
func() *int中,defer修改的是指针指向的原始内存,调用方能看到变化,但要注意悬空指针风险 - 命名返回值在函数声明时就分配了存储空间,
defer函数能访问它;匿名返回值没有名字、没地址,defer无法触达 - 别指望用
defer统一包装错误:如果函数签名是func() error(匿名),defer里改err变量不影响返回值
panic时defer仍执行,但recover必须在defer内
函数发生 panic 后,控制权不会直接跳出,而是照常走完所有已注册的 defer(LIFO 顺序),然后才真正崩溃。这正是 recover 能起作用的前提。
常见错误:
func bad() {
recover() // ❌ 永远不生效
panic("boom")
}或者func bad() {
defer recover() // ❌ recover() 返回值被丢弃,且不在函数体内调用
panic("boom")
}
- 必须这样写:
defer func() { if r := recover(); r != nil { // 处理 panic } }() -
recover()只在defer函数体内调用才有效,且该defer必须在panic发生前注册 - 嵌套函数中,外层
defer对内层panic依然有效,但recover只捕获当前 goroutine 的 panic
真正容易被忽略的是:defer 的注册时机和参数绑定是静态的,而执行时机是动态的——它既不是“马上”,也不是“随便什么时候”,而是在函数生命周期终点那一个精确的环节。写的时候想当然按代码顺序排,运行时却按栈序翻转,参数又早早就被拍了快照,三者叠加,bug 往往静默出现,只在特定路径下暴露。

















