从golang函数栈空间分布看defer,你就不会再错了

    defer 是golang 面试常会面的一个点,但是实在话, 这玩意没多大用,特别是高频下,很多厂的优化点之一就是defer。但是这玩意复杂起来,你确实不一定能都答对,到底怎么分析defer ,才能保证返回值正常呢?其实明白 golang 的函数栈空间布局,就不会再弄错了。

    参考网上一哥们的文章,http://www.zenlife.tk/golang-defer.md,这个兄弟拿了三个例子,总结了一个方法,对于处理带复杂返回值的情况是有用的。

    首先做个测试题,如果全部都能做对,这篇文章就没必要看了,要是感觉有点瞎蒙,就还是看下:

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 的时候,我们拆解成下面步骤:

返回值 = xxx
调用defer函数
空的return

    为什么这样是没问题的,有两个关键点,第一个,golang 的返回值是通过栈空间,不是通过寄存器,这点最重要。调用函数前,首先分配的是返回值空间,然后是入参地址,再是其他临时变量地址。第二点,return 是非原子的,return 操作的确是分三步,将返回值拷贝到栈空间第一块区域,然后再执行defer 操作,最后一个ret 跳转,这个操作的确是可以对应到汇编代码的。然后,这里第二步很巧妙,这里的返回值是否在定义的时候已经命名了?defer 是否能更改栈空间第一块区域的地址的值(是否在defer作用域)?这里画画图立马就能看明白。。。

    看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 的返回值问题不会再错了。

 

展开阅读全文
博主设置当前文章不允许评论。

没有更多推荐了,返回首页