1. 程式人生 > >從golang函式棧空間分佈看defer,你就不會再錯了

從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 的返回值問題不會再錯了。