golang defer詳解
example1
1 2 3 4 5 6 |
|
example2
1 2 3 4 5 6 7 |
|
example3
1 2 3 4 5 6 |
|
先不要執行程式碼,自己在心裡跑一遍結果,然後再去驗證。如果三個都做對了並且不好蒙的...好吧,不用往下看了,你已經懂defer了。
額,如果example1中你算的是0,你就錯了;如果example2中你覺得是10,你又錯了...蒙對的不算...如果example3中覺得得6,你又錯了...如果你有算對的,也有算錯,好吧...你丫的就是在蒙!不懂的繼續往下看啊.....
首先要明確的是:defer是在return之前執行的。這個在 官方文件中明確說明了的。
然後是瞭解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,它可以改寫成這樣:
1 2 3 4 5 6 7 |
|
所以這個返回值是1。
再看example2,它可以改寫成這樣:
1 2 3 4 5 6 7 8 |
|
所以這個的結果是5。
最後看example3,它改寫後變成:
1 2 3 4 5 6 7 |
|
所以這個例子的結果是1。
結論:defer確實是在return之前呼叫的。但表現形式上卻可能不像。本質原因是return xxx語句並不是一條原子指令,defer被插入到了賦值 與 RET之前,因此可能有機會改變最終的返回值。