在 Go 语言中,return 语句并不是一个原子操作。当 defer 和 return 同时出现时,其执行顺序可以拆解为三个步骤。
执行步骤
- 返回值赋值:将
return语句后面的结果值赋给返回值变量(可能是匿名的,也可能是命名的)。 - 执行 defer:按照后进先出(LIFO)的顺序执行所有已注册的
defer函数。 - 真正的 RET 指令:携带当前返回值变量中的值退出函数。
核心结论
defer的执行时机是在 返回值赋值之后,函数真正返回之前。
场景分析
1. 匿名返回值
如果函数是匿名返回值,defer 中修改变量通常不会影响最终结果,因为 defer 修改的是局部变量,而返回值在第一步已经完成了拷贝。
func anonymousReturn() int {
var i int
defer func() {
i++ // 修改的是局部变量 i
}()
return i // 1. 返回值 = i (0); 2. 执行 defer (i=1); 3. 返回 0
}2. 命名返回值
如果函数是命名返回值,defer 直接操作的就是返回值变量,因此修改会生效。
func namedReturn() (i int) {
defer func() {
i++ // 直接修改命名返回值变量 i
}()
return i // 1. i = i (0); 2. 执行 defer (i=1); 3. 返回 1
}3. 指针类型的返回值
无论是匿名还是命名,如果返回的是指针,defer 修改指针指向的内容,最终外部获取到的内容会改变(虽然指针地址没变)。
func pointerReturn() *int {
i := 0
defer func() {
i++
}()
return &i // 返回的是 i 的地址,defer 修改了地址对应的值
}总结
理解 defer 和 return 顺序的关键在于:return 语句会先给返回值赋值,然后再执行 defer。
- 匿名返回值:
return值的快照已生成,defer修改原变量不影响快照。 - 命名返回值:
return赋值的对象就是那个命名变量,defer修改它会直接影响结果。