cgo 不会自动管理 C 分配的内存,必须显式释放,否则必然泄漏;Go GC 对 C 指针无效,因其仅跟踪 Go 堆对象,C 堆内存(如 C.CString)不在可达图中,需手动调用 C.free 或封装 Close 方法确保及时释放。

Go 与 C 互操作时,cgo 不会自动管理 C 分配的内存,必须由开发者显式释放;否则必然泄漏——这不是“可能”,而是确定会发生。
为什么 Go 的 GC 对 C 指针完全无效
Go 的垃圾回收器只跟踪 Go 堆上由 new、make 或字面量创建的对象。C 代码通过 malloc、calloc 或 C 库函数返回的指针(如 C.CString、C.CBytes、或 C 结构体指针)不在 GC 可达图中,GC 根本不知道它们存在。
- 现象:程序长期运行后 RSS 持续上涨,
runtime.ReadMemStats显示HeapAlloc稳定但sys或rss持续增长,大概率是 C 内存未释放 - 典型错误:直接把
C.CString("hello")存进 Go struct,不调用C.free;或在 defer 中用C.free,但 struct 生命周期远超函数作用域 - 关键点:C 内存生命周期必须与 Go 对象生命周期对齐,不能依赖 GC “顺便”回收
C.CString 和 C.CBytes 的释放时机必须手动控制
这两个函数返回的指针指向 C 堆内存,且没有绑定任何 Go 运行时元信息。它们的释放不是可选动作,而是接口契约的一部分。
- 正确做法:在获得指针后,**立刻**决定谁负责释放、何时释放。常见模式是封装成 Go 类型并提供
Close()方法 - 错误示例:
s := C.CString("data"); defer C.free(unsafe.Pointer(s))—— 如果s被保存到全局变量或 channel 中,defer 就失效了 - 安全替代:优先使用
C.GoString或C.GoBytes复制内容到 Go 堆,让 GC 自动管理;仅当性能敏感且数据量大时才保留 C 指针 - 注意:
C.CString分配的内存包含末尾