從GO內存模型與調用協議理解defer closure的坑

資料參考:

問題點

  • 非命名返回值
package main

import "fmt"

func Test() (int) {
    ret := 123
    defer func() {
        fmt.Println("first defer:",ret)
    }()
    defer func() {
        ret++
        fmt.Println("Inner:", ret)
    }()

    return ret
}

func main() {
    fmt.Println(Test())
}

打印結果是123
  • 命名返回值
package main

import "fmt"

func Test() (ret int) {
    ret = 123
    defer func() {
        fmt.Println("first defer:",ret)
    }()
    defer func() {
        ret++
        fmt.Println("Inner:", ret)
    }()

    return ret
}

func main() {
    fmt.Println(Test())
}

打印結果是124

前者123, 後者124? 怎麼理解!html

關鍵點

Go的return語句不是原子指令! 底層被分解爲:git

1. 返回值=xxx
2. 調用defer函數
3. 空的return

其實這種理解也是錯的!golang

根據Go的內存模型及調用協議:函數

  • GO在調用棧(Stack)的方法幀(frame)裏, 在參數(arguments)之上保留返回值(returnValue)的位置.
方法幀(frame):
    返回值0
    返回值1
    ...
    實參0
    實參1
    ...
  • return語句設置返回值. 若是是命名返回值, 則可在defer closure中引用.
  • 執行defer函數棧.

從這個層面理解, return語句也是原子指令.code

通俗說

  • 根據Go的內存模型與調用協議知道, 函數幀會保留前N個位置用於保存返回值. 若是返回值是命名的, 則這幾個位置能夠直接訪問.
  • return語句將值設置到返回值的保留位置. 原則上說return語句也是原子指令.
  • defer棧return語句或函數結束後調用. 若是操做了返回值(命名返回值), 其值是可見的!
    須要明確的是, 非命名返回值通常不受影響.

但也能夠看出, 命名返回值某種程度節省了賦值指令.htm

相關文章
相關標籤/搜索