c.malloc分配的内存必须手动配对调用c.free,go gc无法回收;传go指针给c需确保生命周期可控,否则引发野指针;混用c/c++内存操作或误用释放函数会导致崩溃或泄漏。

CGO里用C.malloc分配的内存必须手动free
Go本身不管理C分配的内存,C.malloc返回的指针完全脱离Go运行时跟踪。不配对调用C.free,就是实打实的内存泄漏——GC完全看不见它。
常见错误现象:runtime: out of memory在长期运行CGO服务中突然出现,pprof看heap却没大对象;或者valgrind报告“definitely lost”字节持续增长。
- 永远不要用
defer C.free(ptr)在非C函数调用栈中清理(比如在Go函数里malloc后defer free),因为C指针可能被传到其他C函数里继续使用 - 如果C函数内部已接管该内存(如作为结构体字段长期持有),free时机必须由C侧逻辑决定,Go侧只负责初始分配
- 用
C.CString得到的指针也得C.free,它底层就是C.malloc+strcpy,不是Go字符串
Go指针传给C前必须确保生命周期可控
把*T或[]byte直接转成unsafe.Pointer传给C,等于把Go堆上某块内存的“访问权”交出去。一旦Go GC回收了这块内存(比如原变量超出作用域、被赋值覆盖),C再读写就是野指针——崩溃不一定立刻发生,但必现未定义行为。
使用场景:需要C库解析一段二进制数据、填充回调结构体、或作为缓冲区供C函数反复读写。
立即学习“go语言免费学习笔记(深入)”;
- 最稳做法:用
C.CBytes复制一份到C堆,用完自己C.free;避免传Go指针 - 若必须传Go指针(如性能敏感场景),得用
runtime.KeepAlive延长原变量生命周期,且确保C函数不会异步保存该指针 -
unsafe.Slice(Go 1.21+)比(*[n]byte)(unsafe.Pointer(p))[:n:n]更安全,但不解决根本生命周期问题
CGO中混用C++ new/delete和C malloc/free会出事
如果你链接的是C++写的库,而代码里用了C.malloc,又试图用C.free释放C++ new出来的内存,或者反过来——多数平台会直接abort。C++运行时和C运行时的堆管理器通常不兼容。
错误信息示例:double free or corruption (out)、malloc_consolidate: invalid chunk size。
- 查清目标C/C++库的内存契约:它分配的内存是否要求调用方用它的
destroy函数?文档里有没有明确说“callxxx_free”? - 优先使用库提供的配套释放函数,哪怕它叫
mylib_buffer_destroy,也不要硬套C.free - CGO导入声明里,如果头文件含
extern "C"包裹,说明是C ABI;若无,且库是C++编译的,务必按C++规则配对内存操作
Go 1.21+ 的//go:cgo_import_dynamic不改变内存管理责任
有人以为用动态链接模式(//go:cgo_import_dynamic)就能让C库自己管内存,其实没用。动态链接只影响符号加载时机,不改变任何内存所有权规则——谁分配,谁负责释放,这条铁律在CGO里纹丝不动。
性能影响:动态链接本身几乎无开销,但若因此误判内存归属(比如觉得“既然是动态库分配的,它肯定自己收”),反而引入隐蔽泄漏。
- 即使库是dlopen加载的,只要它通过函数返回了新分配的内存块,Go侧仍需按其API文档调用对应释放函数
- 检查
nm -D your.so | grep free,确认库是否暴露了专用释放符号;没有的话,大概率要自己C.free - 跨语言调用边界永远是最容易漏掉清理的地方,别依赖“应该有人管”这种假设
最难绷的其实是“看起来没事”:小流量压测不出问题,一上生产,C堆碎片累积、系统级内存耗尽,排查时才发现三年前某处C.malloc漏了C.free。










