码农老张 后端
2024-08-07

C 代码import "C",注释会变颜色C 包调用 sum 函数go 代码解读复制代码package main
/*
int sum(int a, int b) {
return a+b;
}
*/
import "C"
import "fmt"
func main() {
fmt.Println(C.sum(1, 2))
}
go 语言是如何实现调用 C 代码的呢?
运行 go tool cgo main.go 会在当前目录生成 _obj 的文件夹,就可以看到生成的 C 代码了
main.go 文件会被编译成 main.cgo1.go 文件,C.sum 函数会被转换成 _Cfunc_sum 函数go
代码解读复制代码println(( /*line :11:10*/_Cfunc_sum /*line :11:14*/)(1, 2))
_Cfunc_sum 函数在 _cgo_gotypes.go 文件中定义go
代码解读复制代码//go:cgo_unsafe_args
func _Cfunc_sum(p0 _Ctype_int, p1 _Ctype_int) (r1 _Ctype_int) {
_cgo_runtime_cgocall(_cgo_db952ddc3181_Cfunc_sum, uintptr(unsafe.Pointer(&p0)))
if _Cgo_always_false {
_Cgo_use(p0)
_Cgo_use(p1)
}
return
}
_Cfunc_sum 函数调用 _cgo_db952ddc3181_Cfunc_sum 方法
_cgo_db952ddc3181_Cfunc_sum 在 main.cgo2.c 文件中定义c
代码解读复制代码void
_cgo_db952ddc3181_Cfunc_sum(void *v)
{
struct {
int p0;
int p1;
int r;
char __pad12[4];
} __attribute__((__packed__)) *_cgo_a = v;
char *_cgo_stktop = _cgo_topofstack();
__typeof__(_cgo_a->r) _cgo_r;
_cgo_tsan_acquire();
_cgo_r = sum(_cgo_a->p0, _cgo_a->p1);
_cgo_tsan_release();
_cgo_a = (void*)((char*)_cgo_a + (_cgo_topofstack() - _cgo_stktop));
_cgo_a->r = _cgo_r;
_cgo_msan_write(&_cgo_a->r, sizeof(_cgo_a->r));
}
原理:
C 方法C 方法将结果写入返回值的位置go 的调度器需要配合:
C 程序之后,调度器无法抢占协程C 程序之后,go 的线程就没法动弹了,直到 C 方法运行结束协程栈需要切换:
C 的栈用的是系统栈,不受 go runtime 管理C 时,需要将当前栈切换到线程的系统栈上cgo 的优点:cgo 可以让 go 调用现成的 C 实现
cgo 的缺点
cgo 限制了 go 语言的跨平台特性cgo 并不能提高 go 语言的性能defer 有两种情况:
defer 信息,函数退出时调用defer 代码直接编译进函数尾部go 代码解读复制代码func a() {
fmt.Println("1")
}
func main() {
defer a()
}
通过 go build -gcflags -S main.go 查看汇编s
代码解读复制代码0x001a 00026 (/project/tools/limit-go/main.go:11) LEAQ main.a·f(SB), AX
0x0021 00033 (/project/tools/limit-go/main.go:11) MOVQ AX, main..autotmp_1+8(SP)
0x0026 00038 (/project/tools/limit-go/main.go:12) MOVB $0, main..autotmp_0+7(SP)
0x002b 00043 (/project/tools/limit-go/main.go:12) PCDATA $1, $1
0x002b 00043 (/project/tools/limit-go/main.go:12) CALL main.a(SB)
0x0030 00048 (/project/tools/limit-go/main.go:12) ADDQ $16, SP
0x0034 00052 (/project/tools/limit-go/main.go:12) POPQ BP
0x0035 00053 (/project/tools/limit-go/main.go:12) RET
0x0036 00054 (/project/tools/limit-go/main.go:12) CALL runtime.deferreturn(SB)
0x003b 00059 (/project/tools/limit-go/main.go:12) ADDQ $16, SP
0x003f 00063 (/project/tools/limit-go/main.go:12) POPQ BP
0x0040 00064 (/project/tools/limit-go/main.go:12) RET
0x0041 00065 (/project/tools/limit-go/main.go:12) NOP
通过汇编我们可以看到,go 在编译时:
11 行时遇到 defer,将 defer 的信息记录下来12 行, 它调用了 runtime.deferreturn 方法 代码解读复制代码func Add(a, b int) int {
return a + b
}
func Sub(a, b int) int {
return a - b
}
func Call(f func(a int, b int) int) {
v := reflect.ValueOf(f)
if v.Kind() != reflect.Func {
return
}
argv := make([]reflect.Value, 2)
argv[0] = reflect.ValueOf(1)
argv[1] = reflect.ValueOf(2)
result := v.Call(argv)
fmt.Println(result[0].Int())
}
func main() {
Call(Add)
Call(Sub)
}