W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
defer和go一樣都是Go語言提供的關(guān)鍵字。defer用于資源的釋放,會(huì)在函數(shù)返回之前進(jìn)行調(diào)用。一般采用如下模式:
f,err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
如果有多個(gè)defer表達(dá)式,調(diào)用順序類似于棧,越后面的defer表達(dá)式越先被調(diào)用。
不過如果對(duì)defer的了解不夠深入,使用起來可能會(huì)踩到一些坑,尤其是跟帶命名的返回參數(shù)一起使用時(shí)。在講解defer的實(shí)現(xiàn)之前先看一看使用defer容易遇到的問題。
先來看看幾個(gè)例子。例1:
func f() (result int) {
defer func() {
result++
}()
return 0
}
例2:
func f() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}
例3:
func f() (r int) {
defer func(r int) {
r = r + 5
}(r)
return 1
}
請(qǐng)讀者先不要運(yùn)行代碼,在心里跑一遍結(jié)果,然后去驗(yàn)證。
例1的正確答案不是0,例2的正確答案不是10,如果例3的正確答案不是6......
defer是在return之前執(zhí)行的。這個(gè)在 官方文檔中是明確說明了的。要使用defer時(shí)不踩坑,最重要的一點(diǎn)就是要明白,return xxx這一條語句并不是一條原子指令!
函數(shù)返回的過程是這樣的:先給返回值賦值,然后調(diào)用defer表達(dá)式,最后才是返回到調(diào)用函數(shù)中。
defer表達(dá)式可能會(huì)在設(shè)置函數(shù)返回值之后,在返回到調(diào)用函數(shù)之前,修改返回值,使最終的函數(shù)返回值與你想象的不一致。
其實(shí)使用defer時(shí),用一個(gè)簡單的轉(zhuǎn)換規(guī)則改寫一下,就不會(huì)迷糊了。改寫規(guī)則是將return語句拆成兩句寫,return xxx會(huì)被改寫成:
返回值 = xxx
調(diào)用defer函數(shù)
空的return
先看例1,它可以改寫成這樣:
func f() (result int) {
result = 0 //return語句不是一條原子調(diào)用,return xxx其實(shí)是賦值+ret指令
func() { //defer被插入到return之前執(zhí)行,也就是賦返回值和ret指令之間
result++
}()
return
}
所以這個(gè)返回值是1。
再看例2,它可以改寫成這樣:
func f() (r int) {
t := 5
r = t //賦值指令
func() { //defer被插入到賦值與返回之間執(zhí)行,這個(gè)例子中返回值r沒被修改過
t = t + 5
}
return //空的return指令
}
所以這個(gè)的結(jié)果是5。
最后看例3,它改寫后變成:
func f() (r int) {
r = 1 //給返回值賦值
func(r int) { //這里改的r是傳值傳進(jìn)去的r,不會(huì)改變要返回的那個(gè)r值
r = r + 5
}(r)
return //空的return
}
所以這個(gè)例子的結(jié)果是1。
defer確實(shí)是在return之前調(diào)用的。但表現(xiàn)形式上卻可能不像。本質(zhì)原因是return xxx語句并不是一條原子指令,defer被插入到了賦值 與 ret之間,因此可能有機(jī)會(huì)改變最終的返回值。
defer關(guān)鍵字的實(shí)現(xiàn)跟go關(guān)鍵字很類似,不同的是它調(diào)用的是runtime.deferproc而不是runtime.newproc。
在defer出現(xiàn)的地方,插入了指令call runtime.deferproc,然后在函數(shù)返回之前的地方,插入指令call runtime.deferreturn。
普通的函數(shù)返回時(shí),匯編代碼類似:
add xx SP
return
如果其中包含了defer語句,則匯編代碼是:
call runtime.deferreturn,
add xx SP
return
goroutine的控制結(jié)構(gòu)中,有一張表記錄defer,調(diào)用runtime.deferproc時(shí)會(huì)將需要defer的表達(dá)式記錄在表中,而在調(diào)用runtime.deferreturn的時(shí)候,則會(huì)依次從defer表中出棧并執(zhí)行。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: