从GO内存模型与调用协议理解defer closure的坑

资料参考:

问题点

  • 非命名返回值
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语句或函数结束后调用. 如果操作了返回值(命名返回值), 其值是可见的!
    需要明确的是, 非命名返回值一般不受影响.

但也可以看出, 命名返回值某种程度节省了赋值指令.

posted @ 2017-09-20 10:03  zolo®  阅读(275)  评论(0编辑  收藏  举报