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