多个defer按后进先出(LIFO)顺序执行,即最后声明的最先执行;参数在defer语句处即时求值,有名返回值可被defer修改,panic时defer仍执行且recover必须在defer内调用。
多个 defer 是后进先出(LIFO),不是按代码顺序执行
写在前面的 defer 反而最后执行,写在后面的先执行。这不是 bug,是 go 明确设计的栈式行为:每次遇到 defer 语句,就把它压入当前函数的 defer 栈;函数退出(包括 panic)时,从栈顶开始依次弹出并执行。
常见错误现象:看到 defer fmt.Println(1) 写在上面、defer fmt.Println(2) 写在下面,就以为会先打 1 再打 2 —— 实际输出是 2 然后 1。
- 资源清理逻辑更自然:比如先
os.Open,再mu.Lock(),接着写两行defer——defer mu.Unlock()和defer f.Close(),执行时自动先关文件再解锁,和资源获取顺序相反,反而符合“后开先关”直觉 - 循环中注册 defer 要特别小心:每次迭代都 push 一个 defer,最终执行顺序是倒序迭代,容易误判生命周期
- defer 本身不延迟“参数求值”:比如
defer fmt.Println(i)中的i是该行执行时的值,不是函数退出时的值
defer 和 return 的执行时机有明确先后关系
return 不是一条原子指令。它实际分两步:先给返回值赋值(有名返回值此时已存在,匿名返回值此时才声明并赋值),再触发函数退出流程;而退出流程的第一件事,就是逆序执行所有已注册的 defer,最后才真正返回。
这意味着:只有有名返回值能被 defer 修改;匿名返回值在 return 后已被拷贝,defer 里改的是别的变量,不影响最终返回值。
- 有名返回值函数:
func f() (x int) { x = 1; defer func(){x++}(); return }→ 返回2 - 匿名返回值函数:
func f() int { x := 1; defer func(){x++}(); return x }→ 返回1(defer改的是局部变量x,不是返回值) - 指针返回值例外:
func f() *int { i := 0; defer func(){i++}(); return &i }→ 调用方拿到的指针指向的值是1,因为i是栈变量,defer 修改了它原始内存
panic 时 defer 仍会执行,但 recover 必须在 defer 里调用
函数发生 panic 后,控制权不会直接跳出,而是照常走 defer 栈 —— 所有已注册的 defer 都会按 LIFO 顺序执行。这正是 recover 能起作用的前提:它只在 defer 函数内部调用才有效。
立即学习“go语言免费学习笔记(深入)”;
常见错误现象:把 recover() 写在普通代码块里,或者写在 defer 外面的函数里,结果永远返回 nil。
-
defer func(){ if r := recover(); r != nil { /* 处理 panic */ } }()✅ 正确位置 -
if r := recover(); r != nil { ... }❌ 在 defer 外,永远收不到 panic - 多个 defer 都含
recover()时,只有最靠近 panic 触发点的那个(即栈顶第一个执行的 defer)能捕获到,其余的recover()返回nil
复杂点在于 defer 对返回值的影响依赖于函数签名形式,而不是表面的 “return 之后才执行”。很多问题本质是混淆了“返回值变量的存在时机”和“defer 的执行时机”,这两者必须结合着看。


















