cgo
cgo
ToC
- 基础概念与启用
- 语法与类型映射
- 内存与指针规则
- 字符串/字节互转
- 构建与链接(#cgo)
- 调用示例:Go 调 C
- 调用示例:C 调 Go(c-shared/c-archive)
- 并发与线程注意事项
- 性能与测试
- 交叉编译与平台差异
- 常见错误与排查
- 参考链接
基础概念与启用
cgo 允许在 Go 中直接调用 C 代码与库,或将 Go 代码导出给 C 调用。
- 启用:默认在本机构建时开启;通过
CGO_ENABLED=0/1控制 - 依赖编译器:需要系统 C 编译器(如 gcc/clang)
- 入口:
import "C",并在其上方的注释块里写#include、#cgo等预处理指令
示例结构:
/*
#include <stdlib.h>
*/
import "C"
语法与类型映射
- 访问 C 符号:
C.funcName、C.TypeName、C.variable - C 头文件:写在
import "C"前的注释块中使用#include引入 - 常见类型映射(部分):
C.char↔byte/rune(依使用场景),字符串需转换C.int↔C.int(Go 侧保持C.int,或显式转换int(C.int))- 指针:
*C.char、*C.void,用unsafe.Pointer做桥接
内存与指针规则(重要)
Go 与 C 的内存管理不同,必须遵守以下规则:
- 不要把 Go 指针存到 C 内存中(C 中长期保存 Go 指针是禁止的)
- 不要将指向 Go 内存的指针在 C 侧跨越调用生命周期持有
- C 需要长期持有的内存请用
C.malloc分配,并在合适时机C.free - 需要把 Go 值传给 C 保持时,使用
cgo.Handle进行“句柄化”
句柄示例:
import "runtime/cgo"
// 创建句柄并交给 C 持有(以 uintptr 形式)
h := cgo.NewHandle(myGoValue)
// 传给 C: (C.uintptr_t)(uintptr(h))
// 从 C 回传后在 Go 侧恢复:
v := h.Value().(MyType)
// 用完后释放,避免泄漏
h.Delete()
字符串/字节互转
常见转换:
// Go -> C 字符串(需要释放)
cs := C.CString(goStr)
defer C.free(unsafe.Pointer(cs))
// C -> Go 字符串(复制一份)
goStr := C.GoString(cstr)
// C 指针 + 长度 -> Go []byte
bs := C.GoBytes(unsafe.Pointer(ptr), C.int(n))
构建与链接(#cgo)
在 import "C" 上方的注释中使用 #cgo 指定编译/链接参数:
/*
#cgo CFLAGS: -I/usr/local/include/mylib
#cgo LDFLAGS: -L/usr/local/lib -lmylib
#cgo pkg-config: libssl
#include <mylib.h>
*/
import "C"
说明:
CFLAGS/CPPFLAGS:头文件、宏定义等LDFLAGS:库路径、依赖库(顺序重要,通常依赖放后)pkg-config:通过 pkg-config 自动注入 cflags/libs
调用示例:Go 调 C
简单示例:调用 C 的加法与打印。
package main
/*
#include <stdio.h>
static int add(int a, int b) { return a + b; }
*/
import "C"
import (
"fmt"
)
func main() {
s := C.add(3, 5)
C.printf(C.CString("C add: %d\n"), s) // 演示用途,CString 需释放,可分两步写并 free
fmt.Println("Go side: ", int(s))
}
注意:上例中直接内联 C.CString 会泄漏,正确方式:
msg := C.CString("C add: %d\n")
defer C.free(unsafe.Pointer(msg))
C.printf(msg, s)
调用示例:C 调 Go(c-shared/c-archive)
方式一:产出共享库供 C 链接(Linux/macOS):
go build -buildmode=c-shared -o libcalc.so
方式二:产出静态库与头文件:
go build -buildmode=c-archive -o libcalc.a
导出 Go 函数给 C 使用:
package main
/*
#include <stdint.h>
*/
import "C"
//export Sum
func Sum(a, b C.int) C.int {
return a + b
}
func main() {} // c-archive/c-shared 需要 main 包
生成的头文件中会有 int32_t Sum(int32_t a, int32_t b); 等声明,C 侧直接链接调用。
提示://export 需紧贴 Go 函数声明之上;文件内必须包含一次 import "C"。