CGO内存泄漏必须手动干预,因C分配的内存脱离Go GC管理;C.CString/C.CBytes需配对C.free释放,Go指针不可被C长期持有,导出函数不能返回Go内存指针。

CGO 内存泄漏不是 GC 能自动回收的问题,它发生在 Go 与 C 的交界处——C 分配的内存没被 C.free 释放,或 Go 指针被 C 长期持有后失效,这两类问题必须手动干预,否则必然泄漏。
为什么 C.CString 和 C.CBytes 必须配对 C.free
这两个函数底层调用的是 C 的 malloc,分配的内存完全脱离 Go 的 GC 管理范围。不显式释放,就等同于 C 程序中忘了 free。
- 常见错误:只调用
C.CString("hello"),但漏掉C.free(unsafe.Pointer(cs)) - 后果:每秒调用百次,几分钟内内存飙升几十 MB,且永不回落
- 正确写法必须带
defer或确保在作用域末尾释放:cs := C.CString(s) defer C.free(unsafe.Pointer(cs)) // 后续使用 cs
- 注意:
defer只在函数返回时执行,若函数长期运行(如 goroutine 中),需改用显式释放或runtime.SetFinalizer辅助(不推荐用于关键路径)
Go 指针传给 C 后不能“存起来”用
C 函数只能在调用期间安全访问传入的 Go 指针;一旦返回,Go 运行时可能因栈伸缩或 GC 移动该对象,C 再次访问就会读到脏数据或触发崩溃。
- 典型错误:C 代码把
void*存进全局结构体,后续回调时再解引用 - 真实报错现象:
fatal error: unexpected signal during runtime execution或随机 segfault - 安全替代方案:
– 用整数 ID 映射 Go 对象(如sync.Map存map[uint64]*MyStruct)
– C 侧只传 ID,Go 回调时查表取对象
– 使用完毕后主动从 map 中Delete,避免悬垂引用
导出函数(//export)绝不能返回 Go 分配的内存指针
Go 编译器会拒绝这种写法,报错:cgo result has Go pointer to Go pointer 或更隐晦的 invalid memory address or nil pointer dereference。
- 根本原因:C 拿到的指针指向 Go 堆,但 C 不会通知 GC 该对象仍在使用,GC 可能随时回收它
- 正确做法:
– 若需返回字符串,用C.CString分配 C 内存,由 C 侧负责free
– 若需返回结构体数组,用C.malloc分配,同样交由 C 侧释放
– 绝不返回*C.char指向 Go 字符串底层数组,哪怕加了unsafe.String - 验证技巧:在导出函数末尾加
runtime.GC()强制触发回收,能更快暴露问题
最易被忽略的一点:所有通过 C.malloc、C.calloc、C.realloc 分配的内存,无论是否经由 Go 中转,都必须由 C 侧或 Go 中明确调用 C.free —— CGO 不做任何自动跟踪,也不提供 RAII 式封装。漏一次,就多一块永远无法回收的内存。











