cgo的指標傳遞

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

在cgo的官方文檔中有一小節特地介紹了cgo中傳遞c語言和go語言指標之間的傳遞,由於裡面講得比較抽象並且缺少例子,因此通過這篇文章總結cgo指標傳遞的注意事項。

基本概念

在官方文檔和本篇總結中,Go指標指的是指向Go分配的記憶體的指標(例如使用&運算子或者調用new函數擷取的指標)。而C指標指的是C分配的記憶體的指標(例如調用malloc函數擷取的指標)。一個指標是Go指標還是C指標,是根據記憶體如何分配判斷的,與指標的類型無關。

Go調用C

傳遞指向Go Memory的指標

Go調用C Code時,Go傳遞給C Code的Go指標所指的Go Memory中不能包含任何指向Go Memory的Pointer。

值得注意的是,Go是可以傳遞給C Code的Go指標的,但是這個指標裡面不能包含任何指向Go Memory的Pointer。

package main/*#include <stdio.h>struct Foo {    int a;    int *p;};void plusOne(struct Foo *f) {    (f->a)++;    *(f->p)++;}*/import "C"import "unsafe"import "fmt"func main() {    f := &C.struct_Foo{}    f.a = 5    f.p = (*C.int)((unsafe.Pointer)(new(int)))    // f.p = &f.a    C.plusOne(f)    fmt.Println(int(f.a))}

在以上代碼可以看出,Go Code向C Code傳遞了一個指向Go Memory(Go分配的)指標f,但f指向的Go Memory中有一個指標p指向了另一處Go Memory:new(int)。當使用go build編譯這個檔案時,是可以通過編譯的,然後在運行時會發生如下報錯:panic runtime error: cgo argument has Go pointer to Go pointer

傳遞指向struc field的指標

Go調用C Code時,如果傳遞的是一個指向struct field的指標,那麼“Go Memory”專指這個field所佔用的記憶體,即便struct中有其他field指向其他Go Memory也沒關係。

將上面例子改為只傳入指向struct field的指標。如下:

package main/*#include <stdio.h>struct Foo {    int a;    int *p;};void plusOne(int *i) {    (*i)++;}*/import "C"import (    "fmt"    "unsafe")func main() {    f := &C.struct_Foo{}    f.a = 5    f.p = (*C.int)((unsafe.Pointer)(new(int))    C.plusOne(&f.a)    fmt.Println(int(f.a))}

直接指向go run,列印結果為6。可以看出,因為這次調用只傳遞單個field指標,指向這個field所佔用的記憶體,而這個field也沒有嵌套其他指向Go Memory的指標,因此這是符合規範的調用,不會觸發panic

傳遞指向slice或array中的element指標

和傳遞struct field不同,傳遞一個指向slice或者array中的element指標時,需要考慮的Go Memory的範圍不僅僅是這個element,而是整個array或這個slice背後的underlying array所佔用的記憶體地區,要保證整個地區內不包含任何指向Go Memory的指標。

package main/*#include <stdio.h>void plusOne(int **i) {    (**i)++;}*/import "C"import (    "fmt"    "unsafe")func main() {    s1 := make([]*int, 5)    var a int = 5    s1[1] = &a    C.plusOne((**C.int)((unsafe.Pointer)(&s1[0])))    fmt.Println(s1[0])}

從以上代碼可以看出,傳遞給C的是slice第一個element的地址,並不包括指向Go Memory的指標,但由於第二個element儲存了另外一塊Go Memory的地址(&a),當運行go run時,獲得報錯:panic runtime error: cgo argument has Go pointer to Go pointer

C調用Go

返回指向Go分配的記憶體的指標

C調用的Go函數不能返回指向Go分配的記憶體的指標。
package main// extern int* goAdd(int, int);//// int cAdd(int a, int b) {//  int *i = goAdd(a, b);//  return *i;// }import "C"import "fmt"// export goAddfunc goAdd(a, b C.int) {    c := a + b    return &c}func main() {    var a, b int = 5, 6    i := C.cAdd(C.int(a), C.int(b))    fmt.Println(int(i))}

上面代碼中,goAdd這個Go函數返回了一個指向Go分配的記憶體(&c)的指標。運行上述代碼,結果如下:panic runtime error: cgo result has Go pointer

在C分配的記憶體中儲存指向Go分配的記憶體的指標

Go Code不能在C分配的記憶體中儲存指向Go分配的記憶體的指標。
package main// #include <stdlib.h>// extern void goFoo(int**);//// void cFoo() {//  int **p = malloc(sizeof(int*));//  goFoo(p);// }import "C"//export goFoofunc goFoo(p **C.int) {    *p = new(C.int)}func main() {    C.cFoo()}

針對此例,預設的GODEBUG=cgocheck=1是正常啟動並執行,將GODEBUG=cgocheck=2則會發生報錯:fatal error: Go pointer stored into non-Go memory

檢測控制

以上規則會在運行時動態檢測,可以通過設定GODEBUG環境變數修改檢測程度,預設值是GODEBUG=cgocheck=1,可以通過設定為0取消這些檢測,也可以通過設定為2來提高檢測標準,但這會犧牲啟動並執行效率。

此外,也可以通過使用unsafe包來逃脫這些限制,而且C語言方面也沒法使用什麼特殊的機制來限制調用Go。儘管如此,如果程式打破了上面的限制,很可能會以一種無法預料的方式調用失敗。

小結

cgo中,Go與C的記憶體應該保持著相對獨立,指標之間的傳遞應該盡量避免嵌套不同記憶體的指標(如C中儲存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.