defer 是golang 面试常会面的一个点,可是实在话, 这玩意没多大用,特别是高频下,不少厂的优化点之一就是defer。可是这玩意复杂起来,你确实不必定能都答对,到底怎么分析defer ,才能保证返回值正常呢?其实明白 golang 的函数栈空间布局,就不会再弄错了。golang
参考网上一哥们的文章,http://www.zenlife.tk/golang-defer.md,这个兄弟拿了三个例子,总结了一个方法,对于处理带复杂返回值的状况是有用的。面试
首先作个测试题,若是所有都能作对,这篇文章就不必看了,要是感受有点瞎蒙,就仍是看下:bash
ex1:函数
func f() (result int) { defer func() { result++ }() return 0 }
ex2:布局
func f() (r int) { t := 5 defer func() { t = t + 5 }() return t }
ex3:测试
func f() (r int) { defer func(r int) { r = r + 5 }(r) return 1 }
这三个例子基本涵盖了defer 最复杂的状况,并且很是有表明性。优化
那个兄弟说的比较清楚了,他也总结了一个很好的方法,这里我不复述他说的内容,谈下本身的理解,他的方法是这样的,当出现defer 的时候,咱们拆解成下面步骤:code
返回值 = xxx 调用defer函数 空的return
为何这样是没问题的,有两个关键点,第一个,golang 的返回值是经过栈空间,不是经过寄存器,这点最重要。调用函数前,首先分配的是返回值空间,而后是入参地址,再是其余临时变量地址。第二点,return 是非原子的,return 操做的确是分三步,将返回值拷贝到栈空间第一块区域,而后再执行defer 操做,最后一个ret 跳转,这个操做的确是能够对应到汇编代码的。而后,这里第二步很巧妙,这里的返回值是否在定义的时候已经命名了?defer 是否能更改栈空间第一块区域的地址的值(是否在defer做用域)?这里画画图立马就能看明白。。。blog
看ex1,函数栈空间以下图,这里没有入参,返回区域有名 result, result 在defer 的做用域,执行defer 的过程修改了result 的值,直接修改了函数返回值栈空间的值。全部,ex1的结果是1。作用域
再看ex2,函数的栈空间以下:
注意下执行过程,这里的返回值地址r,根本不在defer 的做用域,defer 修改不了r的值,return t = 5 的时候,实际是 第一步:t = 5, 第二步,r = t, 第三步:defer 函数,第四部:ret 。从第四步的时候就再也不修改r 的值了。
最后看ex3就简单了,一样的方法,第一步 r = 1 ,返回值是有名的,这时,defer 入参是r 并非r 地址,并不能修改r ,因此最后return 的值是1。
上面的例子明白了,明白了函数的栈区分布基本defer 的返回值问题不会再错了。