Go 函數調用慣例

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

對比C++,Go不支援重載和預設參數,支援不定長變參,多傳回值,匿名函數和閉包。

C入棧順序和傳回值

之前有個疑問,為什麼Go支援多傳回值,而C不行呢。首先回顧一下C函數調用時的棧空間 程式員的自我修養Ch10-2。函數調用時首先參數和返回地址入棧,其次入棧old ebp和需要儲存的寄存器,之後是函數內部的局部變數和其他資料。兩個指標ebp和esp分別指向返回地址和棧頂。

函數傳回值的傳遞有多種情況。若小於4位元組,傳回值存入eax寄存器,由函數調用方讀取eax。若傳回值5到8位元組,採用eax和edx聯合返回。若大於8個位元組,首先在棧上額外開闢一部分空間temp,將temp對象的地址做為隱藏參數入棧。函數返回時將資料拷貝給temp對象,並將temp對象的地址用寄存器eax傳出。調用方從eax指向的temp對象拷貝內容。

Go的多傳回值實現

C需要多傳回值的時候,通常是顯示的將傳回值存放的地址作為參數傳遞給函數。Go的調用慣例和C不同,Go把ret1和ret2在參數arg1 arg2之前入棧並保留空位,被呼叫者將傳回值放在這兩個空位上。

void f(int arg1, int arg2, int *ret, int *ret2)func f(arg1, arg2 int) (ret1, ret2 int)

所以無論是Go還是C,為了避免函數返回的對象拷貝,最好不要返回大對象。

匿名函數和閉包

匿名函數可以賦值給變數,作為結構體欄位,或者在channel中傳遞。匿名函數作為傳回值賦值給f變數,通過gdb調試時info locals可以查看到f變數的內容是一個地址,info symbol [addr] 可以看到這個地址指向了符號表中的main.test.func1.f符號。返回的匿名函數就是一個儲存了匿名函數地址的對象

func test() func(int) int {return func(x int) int {x += xreturn x}}f := test()f(100) // output: 200

閉包是函數式語言的概念。同樣閉包是一個對象FuncVal{ func_addr, closure_var_point},它包含了函數地址和引用到的變數的地址。現在有個問題,如果變數x是分配在棧上的,函數test返回以後對應的棧就失效了,test返回的匿名函數中變數x將引用一個失效的位置。所以閉包環境中引用的變數不會在棧上分配。Go編譯器通過逃逸分析自動識別出變數的範圍,在堆上分配記憶體,而不是在函數f的棧上。

逃逸分析可以解釋為什麼Go可以返回局部變數的地址,而C不行。

func test() func() {    x := 100    fmt.Printf("x (%p) = %d\n", &x, x)    return func() {        fmt.Printf("x (%p) = %d\n", &x, x)    }}f := test()f() // get same output

參考文章 go基礎篇 匿名函數和閉包函數

defer 延遲調用

defer的實現

goroutine的控制結構裡有一張記錄defer運算式的表,編譯器在defer出現的地方插入了指令 call runtime.deferproc,它將defer的運算式記錄在表中。然後在函數返回之前依次從defer表中將運算式出棧執行,這時插入的指令是call runtime.deferreturn

defer與return

defer在return之前執行的含義是:函數返回時先執行傳回值賦值,然後調用defer運算式,最後執行return。以下例子摘自go-internals,總結的都是使用defer的坑。defer確實是在return前調用的,但由於return 語句並不是原子指令,defer被插入到了賦值和ret之間,因此可能有機會改變最終的傳回值。

func f() (result int) {    defer func() {   // result = 0        result++     // result++    }()              // return 1    return 0}
func f() (r int) {     t := 5           // r = t = 5     defer func() {   // t = t + 5 = 10       t = t + 5      // return 5     }()     return t}
func f() (r int) {    defer func(r int) {  // r = 1          r = r + 5      // internal r = 6    }(r)                 // return 1    return 1}

這個現象是在之前做格式化error輸出的時候發現的。

defer與閉包

defer調用參數x是在defer註冊時求值或複製的,因此以下例子中x在最終調用時仍為10,而由於y是閉包參數,閉包複製的是y變數指標,因此最終y為120,實現了延遲讀取。在實際應用中還可以用指標來實現defer的延遲讀取。

fund test() {  x, y := 10, 20  defer func(i int) {    fmt.Println("defer:", i, y) // output: 10 120  }(x)    x += 10  y += 100  fmt.Println(x, y) // output: 20 120}

defer的效能

簡單的BenchmarkTest測試發現濫用defer可能會導致效能問題,尤其在大迴圈中。

參考文章 Go學習筆記

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.