免费观看又色又爽又黄的小说免费_美女福利视频国产片_亚洲欧美精品_美国一级大黄大色毛片

golang中cgo的示例分析

這篇文章主要為大家展示了“golang中cgo的示例分析”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“golang中cgo的示例分析”這篇文章吧。

成都創新互聯主營陽東網站建設的網絡公司,主營網站建設方案,成都app開發,陽東h5微信平臺小程序開發搭建,陽東網站營銷推廣歡迎陽東等地區企業咨詢

GO調C基本原理CGO是實現Go與C互操作的方式,包括Go調C和C調Go兩個過程。其中Go調C的過程比較簡單。對于一個在C中定義的函數add3,在Go中調用時需要顯式的使用C.add3調用。其中C是在程序中引入的一個偽包

代碼中的import “C”即為在Go中使用的偽包。這個包并不真實存在,也不會被Go的compile組件見到,它會在編譯前被CGO工具捕捉到,并做一些代碼的改寫和樁文件的生成。

CGO 提供了 golang 和 C 語言相互調用的機制。某些第三方庫可能只有 C/C++ 的實現,完全用純 golang 的實現可能工程浩大,這時候 CGO 就派上用場了。可以通 CGO 在 golang 在調用 C 的接口,C++ 的接口可以用 C 包裝一下提供給 golang 調用。被調用的 C 代碼可以直接以源代碼形式提供或者打包靜態庫或動態庫在編譯時鏈接。推薦使用靜態庫的方式,這樣方便代碼隔離,編譯的二進制也沒有動態庫依賴方便發布也符合 golang 的哲學。

基本數值類型

golang 的基本數值類型內存模型和 C 語言一樣,就是連續的幾個字節(1 / 2 / 4 / 8 字節)。因此傳遞數值類型時可以直接將 golang 的基本數值類型轉換成對應的 CGO 類型然后傳遞給 C 函數調用,反之亦然:

package main

/*

#include <stdint.h>

static int32_t add(int32_t a, int32_t b) {

    return a + b;

}

*/

import "C"

import "fmt"

func main() {

    var a, b int32 = 1, 2

    var c int32 = int32(C.add(C.int32_t(a), C.int32_t(b)))

    fmt.Println(c) // 3

}

golang 和 C 的基本數值類型轉換對照表如下:

C語言類型 CGO類型 Go語言類型

char C.char byte

singed char C.schar int8

unsigned char C.uchar uint8

short C.short int16

unsigned short C.ushort uint16

int C.int int32

unsigned int C.uint uint32

long C.long int32

unsigned long C.ulong uint32

long long int C.longlong int64

unsigned long long int C.ulonglong uint64

float C.float float32

double C.double float64

size_t C.size_t uint

注意 C 中的整形比如 int 在標準中是沒有定義具體字長的,但一般默認認為是 4 字節,對應 CGO 類型中 C.int 則明確定義了字長是 4 ,但 golang 中的 int 字長則是 8 ,因此對應的 golang 類型不是 int 而是 int32 。為了避免誤用,C 代碼最好使用 C99 標準的數值類型

golang 中切片用起來有點像 C 中的數組,但實際的內存模型還是有點區別的。C 中的數組就是一段連續的內存,數組的值實際上就是這段內存的首地址。golang 切片的內存模型如下所示(參考源碼 $GOROOT/src/runtime/chan.go

array len cap

  |

  v 

data

由于底層內存模型的差異,不能直接將 golang 切片的指針傳給 C 函數調用,而是需要將存儲切片數據的內部緩沖區的首地址及切片長度取出傳傳遞:

package main

/*

#include <stdint.h>

static void fill_255(char* buf, int32_t len) {

    int32_t i;

    for (i = 0; i < len; i++) {

        buf[i] = 255;

    }

}

*/

import "C"

import (

    "fmt"

    "unsafe"

)

func main() {

    b := make([]byte, 5)

    fmt.Println(b) // [0 0 0 0 0]

    C.fill_255((*C.char)(unsafe.Pointer(&b[0])), C.int32_t(len(b)))

    fmt.Println(b) // [255 255 255 255 255]

}

字符串

golang 的字符串和 C 中的字符串在底層的內存模型也是不一樣的:

golang 字串符串并沒有用 '\0' 終止符標識字符串的結束,因此直接將 golang 字符串底層數據指針傳遞給 C 函數是不行的。一種方案類似切片的傳遞一樣將字符串數據指針和長度傳遞給 C 函數后,C 函數實現中自行申請一段內存拷貝字符串數據然后加上未層終止符后再使用。更好的方案是使用標準庫提供的 C.CString() 將 golang 的字符串轉換成 C 字符串然后傳遞給 C 函數調用:

package main

/*

#include <stdint.h>

#include <stdlib.h>

#include <string.h>

static char* cat(char* str1, char* str2) {

    static char buf[256];

    strcpy(buf, str1);

    strcat(buf, str2);

    return buf;

}

*/

import "C"

import (

"fmt"

"unsafe"

)

func main() {

str1, str2 := "hello", " world"

// golang string -> c string

cstr1, cstr2 := C.CString(str1), C.CString(str2)

defer C.free(unsafe.Pointer(cstr1)) // must call

defer C.free(unsafe.Pointer(cstr2))

cstr3 := C.cat(cstr1, cstr2)

// c string -> golang string

str3 := C.GoString(cstr3)

fmt.Println(str3) // "hello world"

}

需要注意的是 C.CString() 返回的 C 字符串是在堆上新創建的并且不受 GC 的管理,使用完后需要自行調用 C.free() 釋放,否則會造成內存泄露,而且這種內存泄露用前文中介紹的 pprof 也定位不出來。

其他類型

golang 中其他類型(比如 map) 在 C/C++ 中并沒有對等的類型或者內存模型也不一樣。傳遞的時候需要了解 golang 類型的底層內存模型,然后進行比較精細的內存拷貝操作。傳遞 map 的一種方案是可以把 map 的所有鍵值對放到切片里,然后把切片傳遞給 C++ 函數,C++ 函數再還原成 C++ 標準庫的 map 。由于使用場景比較少,這里就不贅述了。

總結

本文主要介紹了在 golang 中使用 CGO 調用 C/C++ 接口涉及的一些細節問題。C/C++ 比較底層的語言,需要自己管理內存。使用 CGO 時需要對 golang 底層的內存模型有所了解。另外 goroutine 通過 CGO 進入到 C 接口的執行階段后,已經脫離了 golang 運行時的調度并且會獨占線程,此時實際上變成了多線程同步的編程模型。如果 C 接口里有阻塞操作,這時候可能會導致所有線程都處于阻塞狀態,其他 goroutine 沒有機會得到調度,最終導致整個系統的性能大大較低。總的來說,只有在第三方庫沒有 golang 的實現并且實現起來成本比較高的情況下才需要考慮使用 CGO ,否則慎用。

可以使用go tool cgo在本地目錄生成這些樁文件

$go tool cgo main.go

.

|_____obj

| |_____cgo_.o

| |_____cgo_export.c

| |_____cgo_export.h

| |_____cgo_flags

| |_____cgo_gotypes.go

| |_____cgo_main.c

| |____main.cgo1.go

| |____main.cgo2.c

|____main.go

其中main.cgo1.go為主要文件,是用戶代碼main.go被cgo改寫之后的文件:

$cat _obj/main.cgo1.go

// Created by cgo - DO NOT EDIT

//line /Users/didi/goLang/src/github.com/xiazemin/cgo/exp1/main.go:1

package main

//line /Users/didi/goLang/src/github.com/xiazemin/cgo/exp1/main.go:11

import "fmt"

func main() {

var a, b int32 = 1, 2

var c int32 = int32(_Cfunc_add(_Ctype_int32_t(a), _Ctype_int32_t(b)))

fmt.Println(c)

}

這個文件才是Go的compile組件真正看到的用戶代碼。可以看到原來文件中的import “C”被去掉,而用戶寫的C.int被改寫為_Ctype_int,C.add3被改寫為_Cfunc_add3。關于這個特性有兩個點需要注意。一是在有import “C”的文件中,用戶的注釋信息全部丟失,使用的一些progma也不例外。二是在testing套件中import “C”不允許使用,表現為testing不支持CGO。但并不是沒有辦法在testing中使用CGO,可以利用上面的特性,在另外一個獨立的Go文件中定義C函數,并使用import “C”;但是在使用testing的Go文件中直接使用_Cfunc_add3函數即可。_Cfunc_add3用戶雖然沒有顯示定義,但是CGO自動產生了這一函數的定義。上面一系列的//line編譯制導語句用做關聯生成的Go與原來的用戶代碼的行號信息。

再次回到_Cfunc_add3函數,并不是C中的add3函數,是CGO產生的一個Go函數。它的定義在CGO產生的樁文件_cgo_gotypes.go中

$cat _obj/_cgo_gotypes.go

// Created by cgo - DO NOT EDIT

package main

import "unsafe"

import _ "runtime/cgo"

import "syscall"

var _ syscall.Errno

func _Cgo_ptr(ptr unsafe.Pointer) unsafe.Pointer { return ptr }

//go:linkname _Cgo_always_false runtime.cgoAlwaysFalse

var _Cgo_always_false bool

//go:linkname _Cgo_use runtime.cgoUse

func _Cgo_use(interface{})

type _Ctype_int int32

type _Ctype_int32_t _Ctype_int

type _Ctype_void [0]byte

//go:linkname _cgo_runtime_cgocall runtime.cgocall

func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32

//go:linkname _cgo_runtime_cgocallback runtime.cgocallback

func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr, uintptr)

//go:linkname _cgoCheckPointer runtime.cgoCheckPointer

func _cgoCheckPointer(interface{}, ...interface{})

//go:linkname _cgoCheckResult runtime.cgoCheckResult

func _cgoCheckResult(interface{})

//go:cgo_import_static _cgo_3a42ad434848_Cfunc_add

//go:linkname __cgofn__cgo_3a42ad434848_Cfunc_add _cgo_3a42ad434848_Cfunc_add

var __cgofn__cgo_3a42ad434848_Cfunc_add byte

var _cgo_3a42ad434848_Cfunc_add = unsafe.Pointer(&__cgofn__cgo_3a42ad434848_Cfunc_add)

//go:cgo_unsafe_args

func _Cfunc_add(p0 _Ctype_int32_t, p1 _Ctype_int32_t) (r1 _Ctype_int32_t) {

_cgo_runtime_cgocall(_cgo_3a42ad434848_Cfunc_add, uintptr(unsafe.Pointer(&p0)))

if _Cgo_always_false {

_Cgo_use(p0)

_Cgo_use(p1)

}

return

}

_Cfunc_add3的參數傳遞與正常的函數有些不同,其參數并不在棧上,而是在堆上。函數中的_Cgo_use,其實是runtime.cgoUse,用來告訴編譯器要把p0, p1, p2逃逸到堆上去,這樣才能較為安全的把參數傳遞到C的程序中去。(因為go是動態棧不安全)

$    go build -gcflags "-m" main.go

# command-line-arguments

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:14:6: can inline _Cgo_ptr

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:14:35: leaking param: ptr to result ~r1 level=0

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:27:6: _cgo_runtime_cgocall assuming arg#2 is unsafe uintptr

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:30:6: _cgo_runtime_cgocallback assuming arg#3 is unsafe uintptr

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:30:6: _cgo_runtime_cgocallback assuming arg#4 is unsafe uintptr

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:47:11: p0 escapes to heap

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:48:11: p1 escapes to heap

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:45:75: _Cfunc_add &p0 does not escape

./main.go:16: c escapes to heap

./main.go:16: main ... argument does not escape

函數中的__cgo_79f22807c129_Cfunc_add3是一個變量,記錄了一個C函數的地址(注意,這并不是實際要調用add3函數),是一個真正定義在C程序中的函數。在Go中,通過編譯制導語句//go:cgo_import_static在鏈接時拿到C中函數__cgo_79f22807c129_Cfunc_add3的地址,然后通過編譯制導語句//go:linkname把這個函數地址與Go中的byte型變量__cgofn_cgo_79f22807c129_Cfunc_add3的地址對齊在一起。之后再利用一個新的變量__cgo_79f22807c129_Cfunc_add3記錄這個byte型變量的地址。從而可以實現在Go中拿到C中函數的地址。做完,這些之后把C的函數地址和參數地址傳給cgocall函數,進行Go與C之間call ABI操作。當然,cgocall里面會做一些調度相關的準備動作,后面有詳細說明。

__cgo_79f22807c129_Cfunc_add3如上文所述,是定義在main.cgo2.c中的一個函數,其定義如下:

CGO_NO_SANITIZE_THREAD

void

_cgo_3a42ad434848_Cfunc_add(void *v)

{

struct {

int32_t p0;

int32_t p1;

int32_t r;

char __pad12[4];

} __attribute__((__packed__)) *a = v;

char *stktop = _cgo_topofstack();

__typeof__(a->r) r;

_cgo_tsan_acquire();

r = add(a->p0, a->p1);

_cgo_tsan_release();

a = (void*)((char*)a + (_cgo_topofstack() - stktop));

a->r = r;

}

在這個函數的定義中,并沒有顯式的參數拷貝;而是利用類型強轉,在C中直接操作Go傳遞過來的參數地址。在這個函數中真正調用了用戶定義的add3函數。

            cgocall即_Cfunc_add3中的_cgo_runtime_cgocall函數,是runtime中的一個從Go調C的關鍵函數。這個函數里面做了一些調度相關的安排。之所以有這樣的設計,是因為Go調入C之后,程序的運行不受Go的runtime的管控。一個正常的Go函數是需要runtime的管控的,即函數的運行時間過長會導致goroutine的搶占,以及GC的執行會導致所有的goroutine被拉齊。C程序的執行,限制了Go的runtime的調度行為。為此,Go的runtime會在進入到C程序之后,會標記這個運行C的線程排除在runtime的調度之后,以減少這個線程對Go的調度的影響。此外,由于正常的Go程序運行在一個2K的棧上,而C程序需要一個無窮大的棧。這樣的設計會導致在Go的棧上執行C函數會導致棧的溢出,因此在進去C函數之前需要把當前線程的棧從2K的棧切換到線程本身的系統棧上。棧切換發生在asmcgocall中,而線程的狀態標記發生在cgocall中。

以上是“golang中cgo的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注創新互聯行業資訊頻道!

本文標題:golang中cgo的示例分析
分享URL:http://m.newbst.com/article20/pjdpco.html

成都網站建設公司_創新互聯,為您提供建站公司網頁設計公司網站制作網站改版品牌網站設計網站收錄

廣告

聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯

商城網站建設