从GO内存模型与调用协议理解defer closure的坑
资料参考:
- 官网defer介绍: https://blog.golang.org/defer-panic-and-recover
- 深入解析go:
问题点
- 非命名返回值
package main
import "fmt"
func Test() (int) {
ret := 123
defer func() {
fmt.Println("first defer:",ret)
}()
defer func() {
ret++
fmt.Println("Inner:", ret)
}()
return ret
}
func main() {
fmt.Println(Test())
}
打印结果是123
- 命名返回值
package main
import "fmt"
func Test() (ret int) {
ret = 123
defer func() {
fmt.Println("first defer:",ret)
}()
defer func() {
ret++
fmt.Println("Inner:", ret)
}()
return ret
}
func main() {
fmt.Println(Test())
}
打印结果是124
前者123, 后者124? 怎么理解!
关键点
Go的return语句不是原子指令! 底层被分解为:
1. 返回值=xxx
2. 调用defer函数
3. 空的return
其实这种理解也是错的!
根据Go的内存模型及调用协议:
- GO在调用栈(Stack)的方法帧(frame)里, 在参数(arguments)之上保留返回值(returnValue)的位置.
方法帧(frame):
返回值0
返回值1
...
实参0
实参1
...
- return语句设置返回值. 如果是命名返回值, 则可在defer closure中引用.
- 执行defer函数栈.
从这个层面理解, return语句也是原子指令.
通俗说
- 根据Go的内存模型与调用协议知道, 函数帧会保留前N个位置用于保存返回值. 如果返回值是命名的, 则这几个位置可以直接访问.
- return语句将值设置到返回值的保留位置. 原则上说return语句也是原子指令.
- defer栈return语句或函数结束后调用. 如果操作了返回值(命名返回值), 其值是可见的!
需要明确的是, 非命名返回值一般不受影响.
但也可以看出, 命名返回值某种程度节省了赋值指令.