defer關鍵字
3.4 defer關鍵字
轉載於:https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.4.html
defer和go一樣都是Go語言提供的關鍵字。defer用於資源的釋放,會在函式返回之前進行呼叫。一般採用如下模式:
f,err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
如果有多個defer表示式,呼叫順序類似於棧,越後面的defer表示式越先被呼叫。
不過如果對defer的瞭解不夠深入,使用起來可能會踩到一些坑,尤其是跟帶命名的返回引數一起使用時。在講解defer的實現之前先看一看使用defer容易遇到的問題。
defer使用時的坑
先來看看幾個例子。例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
}
請讀者先不要執行程式碼,在心裡跑一遍結果,然後去驗證。
例1的正確答案不是0,例2的正確答案不是10,如果例3的正確答案不是6......
defer是在return之前執行的。這個在 官方文件中是明確說明了的。要使用defer時不踩坑,最重要的一點就是要明白,return xxx這一條語句並不是一條原子指令!
函式返回的過程是這樣的:先給返回值賦值,然後呼叫defer表示式,最後才是返回到呼叫函式中。
defer表示式可能會在設定函式返回值之後,在返回到呼叫函式之前,修改返回值,使最終的函式返回值與你想象的不一致。
其實使用defer時,用一個簡單的轉換規則改寫一下,就不會迷糊了。改寫規則是將return語句拆成兩句寫,return xxx會被改寫成:
返回值 = xxx
呼叫defer函式
空的return
先看例1,它可以改寫成這樣:
func f() (result int) {
result = 0 //return語句不是一條原子呼叫,return xxx其實是賦值+ret指令
func() { //defer被插入到return之前執行,也就是賦返回值和ret指令之間
result++
}()
return
}
所以這個返回值是1。
再看例2,它可以改寫成這樣:
func f() (r int) {
t := 5
r = t //賦值指令
func() { //defer被插入到賦值與返回之間執行,這個例子中返回值r沒被修改過
t = t + 5
}()
return //空的return指令
}
所以這個的結果是5。
最後看例3,它改寫後變成:
func f() (r int) {
r = 1 //給返回值賦值
func(r int) { //這裡改的r是傳值傳進去的r,不會改變要返回的那個r值
r = r + 5
}(r)
return //空的return
}
所以這個例子的結果是1。
defer確實是在return之前呼叫的。但表現形式上卻可能不像。本質原因是return xxx語句並不是一條原子指令,defer被插入到了賦值 與 ret之間,因此可能有機會改變最終的返回值。
defer的實現
defer關鍵字的實現跟go關鍵字很類似,不同的是它呼叫的是runtime.deferproc而不是runtime.newproc。
在defer出現的地方,插入了指令call runtime.deferproc,然後在函式返回之前的地方,插入指令call runtime.deferreturn。
普通的函式返回時,彙編程式碼類似:
add xx SP
return
如果其中包含了defer語句,則彙編程式碼是:
call runtime.deferreturn,
add xx SP
return
goroutine的控制結構中,有一張表記錄defer,呼叫runtime.deferproc時會將需要defer的表示式記錄在表中,而在呼叫runtime.deferreturn的時候,則會依次從defer表中出棧並執行。
相關文章
- DM 關鍵字、遮蔽關鍵字
- let關鍵字和const關鍵字
- final關鍵字和static關鍵字
- this關鍵字
- 關鍵字
- out關鍵字和ref關鍵字的區別
- abstract關鍵字 super 關鍵字 類與繼承繼承
- 4關鍵字
- [JavaScript] this 關鍵字JavaScript
- synchronized 關鍵字synchronized
- Volatile關鍵字
- static關鍵字
- final關鍵字
- super關鍵字
- const關鍵字
- Voliate關鍵字
- Auto關鍵字
- Swift 關鍵字Swift
- typedef關鍵字
- params關鍵字
- throw關鍵字
- dynamic關鍵字
- volatile 關鍵字
- @Transient關鍵字
- friend關鍵字
- synchronize關鍵字 原理
- 各類關鍵字
- Google關鍵字工具Go
- Java native關鍵字Java
- java中this關鍵字Java
- volatile關鍵字解析
- volidate關鍵字解析
- Synchronize 關鍵字原理
- [java]transient關鍵字Java
- Volatile關鍵字剖析
- 使用 Synchronized 關鍵字synchronized
- c#關鍵字C#
- Java Final關鍵字Java