defer参数在注册时求值而非执行时,值类型拷贝值、引用类型拷贝地址;循环中直接defer读i会输出全为最后一个值,需显式传参或局部变量捕获当时值;defer按LIFO顺序执行,panic时仍执行且recover必须置于defer函数内。

defer参数在注册时就求值,不是执行时
这是最常踩的坑:写 defer fmt.Println(i),以为它会在函数退出时读 i 的最新值,其实 i 在这行语句执行到那一刻就被拷贝固定了。值类型(int、string)拷贝值,指针或切片等引用类型拷贝地址——改内容会影响 defer 执行结果,但改变量本身不会。
常见错误现象:循环中直接写 for i := range s { defer fmt.Println(i) },输出全是最后一个 i 值(比如 2 2 2),不是 0 1 2。
- 想捕获“当时”的值 → 直接传参:
defer fmt.Println(i) - 想捕获“最后”的值 → 用闭包:
defer func() { fmt.Println(i) }() - 循环中安全做法:显式传参
defer func(n int) { fmt.Println(n) }(i)或先赋局部变量j := i; defer fmt.Println(j)
defer函数体里的变量是延迟读取的
参数求值是即时的,但函数体内部所有表达式(包括变量访问、赋值、方法调用)都拖到真正执行时才算。比如这段代码:
defer func() { t.q = t.q }()
t.q = t.m
看似在恢复旧值,实际执行时 t.q 已经是 t.m,所以等于自赋值。真正要保存快照,得靠参数或局部变量:
立即学习“go语言免费学习笔记(深入)”;
- ✅ 正确:
defer func(q int) { t.q = q }(t.q)——t.q在defer行就被读取并传入 - ✅ 等效:
qOrig := t.q; defer func() { t.q = qOrig }() - ❌ 错误:闭包里直接读结构体字段,没做任何快照,就等于没保存
多个defer按LIFO顺序执行,但参数求值互不影响
defer 是压栈行为,后写的先执行。比如:
defer fmt.Println("a")
defer fmt.Println("b")
defer fmt.Println("c")
输出一定是 c b a。这个顺序决定了资源释放是否合理——比如打开文件和关闭文件必须配对,子连接要先关、主连接后关,否则可能触发 use of closed network connection。
- 注册顺序决定执行顺序,不能靠代码书写位置“猜”逻辑
- 每个
defer的参数求值独立发生在各自语句执行时刻,彼此不干扰 - 命名返回值(如
func() (ret int))能被defer修改,匿名返回值不能——因为前者是同一内存位置,后者在return时已拷贝
panic时defer照样执行,recover必须放在defer里
defer 在 return 赋值之后、函数真正退出之前运行,panic 也走这条路。所以 recover() 必须写在 defer 函数体内才有效,写在外面或普通代码里根本抓不到。
- ✅ 正确:
defer func() { recover() }() - ❌ 错误:
recover()单独写在函数开头或中间,永远不生效 - 注意:
defer栈是函数私有、goroutine 绑定的,底层是_defer结构链表,不是全局队列
真正容易被忽略的是:参数求值和函数体执行是两个阶段,且严格分离。写 defer 时,脑子里得切两刀——哪部分现在就要算,哪部分留到最后一刻再跑。


















