從golang函數棧空間分佈看defer,你就不會再錯了

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

相關文章
相關標籤/搜索