前端仔有点学不明白 golang 的 defer

204 天前
 zhengfan2016

背景:这个地方的 test-1 题 https://golang.dbwu.tech/traps/defer_exam/

如下 test-1 题,使用具名返回值,defer 就能修改 t 的值

package main

func foo(n int) (t int) {
	t = n
	defer func() {
		t += 3
	}()
	return t
}

func main() {
	println(foo(1))
}

但是我不使用具名,就算我把 t 移到最外层的作用域,defer 也改变不了 t 的值,我试着不在 defer 作用域内,就可以修改

package main

var t int

func foo(n int) int {
	t = n
	defer func() {
		t += 3
	}()
	return t
}

func main() {
	println(foo(1))
}

感觉被绕晕了

4515 次点击
所在节点    Go 编程语言
46 条回复
Feiir
203 天前
你只要记住当你声明 (t int) 作为返回值时,t 是一个与返回值绑定的变量就行了,没有具名返回值的话,t 就没和返回值绑定,在返回的那一刻就确定值了。
lxdlam
203 天前
这个问题非常得“巧妙”,因为他混合了三个东西,把这个结果拖向了一个“记住就行”的深渊。

1. 返回值的处理:根据 Go 关于 [Return Value]( https://go.dev/ref/spec#Return_statements) 的规范,当你声明一个返回值的时候,你实际上是声明了一个临时对象,区别仅存在于这个返回对象是有名字还是没有名字的;
2. Defer 的作用时机:根据 Go 关于 [Defer Statement]( https://go.dev/ref/spec#Defer_statements) 的规范,`defer` 的作用时机在 `return` 的所有 Value 都被计算且赋值完毕后,真实返回前执行;
3. Go 对闭包的处理:根据 Go 关于 [Function literals]( https://go.dev/ref/spec#Function_literals) 的规范,闭包内捕获的自由变量会被共享;换句话说,你可以理解为闭包实际上捕获了外部变量的指针,对其的修改会同步到原始对象。

花点时间理解上面三个规范会带来的代码作用。

现在我们来分析代码:
- 在 Case1 中,由于返回值被具名了,`return t` 实际上可以理解为 `t = t; return`,也就是仅仅重新赋值,之后执行的 `defer` 重新修改了 `t`,导致返回的值产生了变动。
- 在 Case2 中,由于返回值匿名,假定返回值是一个隐藏变量 `tForReturn`,`return t` 实际上可以理解为 `tForReturn = t; return`,此时虽然你的 `defer` 修改了 `t`,但是由于返回的对象是 `tForReturn`,获取的返回值并没有发生变化,一切正常。当然,此时你再次在 `foo` 调用后查看 `t` 的值,它确实也会是 `4`,`defer` 的作用生效了。

P.S. Go 的规范对这种行为没有明确的规定,上面的三个 spec 其实也只能说是“模糊”描述了作用原理,还是要观察编译器的实现实锤,这也是这门语言天天开天窗擦屁股的核心包袱之一;具名返回值这个特性本身也有很强的“拍脑袋”属性,它确实有用,但是没有有用到这个程度,结果反而引入了更多的混淆。
DOGOOD
203 天前
别学了,这种代码再学下去脑子该坏了。
duzhuo
203 天前
人肉编译器哈哈
onnethy
202 天前
说实话啊 这两段代码直接扔到 ds 里,给你讲的明明白白了
虽然我看也你的代码 也绕得很 但是 ds 倒是给我讲明白了
whinyStone
197 天前
具名返回那种应该是在栈内存提前指定了返回值的位置,所以 defer 执行的时候可以改到那个位置;
后面的返回值一般可能是寄存器传递的,汇编层面会是先算出来,然后执行 defer 的函数,然后弹栈返回;
即使是栈内存传递,也会有一次复制,defer 仍改不到返回值,可以改一个大数组看看;
堆内存就不一样了,在 foo 里声明一个切片并返回它,在 defer 里做修改,返回后的拿到的切片当然是修改后的

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1122635

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX