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