golang的defer精析

關於golang的defer,先看四個例子,在心裏默默計算一下返回值:

example1

func f1() (result int) { 
    defer func() { 
        result++ 
    }() 
    return 0
}


example2

func f2() (r int) { 
    t := 5 
    defer func() { 
        t = t + 5 
    }() 
    return t
}


example3

func f3() (r int) { 
    defer func(r int) { 
        r = r + 5 
    }(r) 
    return 1
}


example4

func f4() (r int) { 
    defer func() { 
        r = r + 5 
    }() 
    return 1
}


先不要運行代碼,自己在心裏跑一遍結果。然後再去驗證。如果四個都做對了並且不是蒙的....好吧,不用往下看了,你已經懂defer了。



如果example1中你算的是0,你就錯了

如果example2中你覺得是10,你又錯了...蒙對的不算...

如果example3中覺得得6,你又錯了...如果你有算對的,也有算錯,好吧...你丫的就是在蒙!

如果example4中覺得得6,那有沒有發現和example3相比,程序有什麼不一樣?

不懂的繼續往下看啊.....


首先要明確的是:defer是在return之前執行的

這是官方文檔中明確說明的http://golang.org/ref/spec#Defer_statements ,知道就行了,可以無視


然後要了解是的defer的實現方式

大意就是在defer出現的地方插入的指令

CALL runtime.deferproc

然後在函數返回之前的地方,插入指令

CALL runtime.deferreturn


再就是明確go返回值的方式跟C是不一樣的,爲了支持多值返回,go是用棧返回值的,而C是用寄存器。


最最最重要的一點就是:return xxx 這一句語句並不是一條原子指令!

整個return過程,沒有defer之前是,先把在棧中寫一個值,這個值被會當作返回值。然後再調用RET指令返回。return xxx語句彙編後是先給返回值賦值,再做一個空的return: ( 賦值指令 + RET指令)

defer的執行是被插入到return指令之前的

有了defer之後,就變成了 (賦值指令 + CALL defer指令 + RET指令)

而在CALL defer函數中,有可能將最終的返回值改寫了...也有可能沒改寫。總之,如果改寫了,那麼看上去就像defer是在return xxx之後執行的~

這是所有你所想不明白的defer故事發生的根源。


上面的基礎知識都有了,然後就可以來說說神奇的defer了。告訴大家一個簡單的轉換規則大家就再也不爲defer迷糊了。

改寫規則是將return語句分開成兩句寫,return xxx會被改寫成:

返回值 = xxx

調用defer函數

空的return


先看example1。它可以改寫成這樣:

func f1() (result int) { 
    result = 0 //return語句不是一條原子調用,return xxx其實是賦值+RET指令 
    func() { //defer被插入到return之前執行,也就是賦返回值和RET指令之間 
        result++ 
    }() 
    return
}

所以這個返回的是1


再看example2。它可以改寫成這樣:

func f2() (r int) { 
    t := 5 
    r = t //賦值指令 
    func() { //defer被插入到賦值與返回之間執行,這個例子中返回值r沒被修改過 
        t = t + 5 
    } 
    return //空的return指令
}

所以這個的結果是5


最後看example3。它改寫後變成:

func f3() (r int) { 
    r = 1 //給返回值賦值 
    func(r int) { //這裏改的r是傳值傳進去的r,不會改變要返回的那個r值 
        r = r + 5 
    }(r) 
    return //空的return
}

所以這個例子結果是1


example4,它改寫後變成:

它改寫後變成:

func f4() (r int) { 
    r = 1 //給返回值賦值 
    func() { //這裏改的r是傳值傳進去的r,不會改變要返回的那個r值 
        r = r + 5 
    }() 
    return //空的return
}

所以這個例子的結果是6


結論:defer確實是在return之前調用的。但表現形式上卻可能不像。本質原因是return xxx語句並不是一條原子指令,defer被插入到了賦值 與 RET之前,因此可能有機會改變最終的返回值。

當你覺得迷糊時,可以用我給的這套規則轉一下代碼。

本文主體參考了文章:https://studygolang.com/articles/742

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章