C.free仅能释放C标准库分配的内存,误用于Go分配或第三方库指针将导致段错误;必须严格匹配分配与释放函数,确保unsafe.Pointer直接来自C分配函数。

CGO里用C.free释放C内存前必须确认指针来源
Go不能直接管理C分配的内存,C.free只负责释放由C.malloc、C.calloc或C.strdup等C标准库函数返回的指针。如果传入一个Go分配的*C.char(比如从C.CString转来的但还没被C接管)、或来自第三方C库内部malloc但未暴露分配细节的指针,调用C.free会触发段错误或内存破坏。
- 安全场景:只有
ptr := C.CString("hello"); defer C.free(unsafe.Pointer(ptr))这类明确由C分配、且你持有原始指针时才可free - 危险场景:
buf := C.get_buffer(); C.free(unsafe.Pointer(buf))——除非文档明确说get_buffer用malloc分配,否则大概率崩 - 更隐蔽的坑:某些C库返回的指针是静态缓冲区或栈上地址,
C.free不仅无效,还会让后续调用行为不可预测
Go字符串转C字符串后,别把C.CString结果当长期指针用
C.CString本质是C.malloc + strcpy,它返回的*C.char必须手动C.free,但它不是“绑定”到Go字符串生命周期的。一旦Go字符串被GC或重用底层数组,原C指针仍有效——但这恰恰是陷阱:你可能误以为只要Go字符串还活着,C指针就安全,其实两者完全无关。
- 常见错误:把
C.CString(s)结果存进struct字段,之后在另一个goroutine里free,但此时s早已被回收,指针虽没变但内容可能被覆盖 - 正确做法:在C函数调用完成**立刻**free,或用
runtime.SetFinalizer兜底(但不推荐,finalizer执行时机不确定) - 替代方案:如果C函数只读,优先用
C.CBytes([]byte(s))并配合unsafe.Slice转*C.char,避免多一次拷贝;但注意C.CBytes返回的也是malloc内存,同样要free
用unsafe.Pointer转换时,类型对齐和大小必须严格匹配
调用C.free本身不检查指针类型,但若你在转换过程中搞错size或对齐(比如把*C.int当*C.char传给C.free),不会报错,但会导致后续malloc/free链表损坏——这种问题往往延迟爆发,排查极难。
- 典型错误:
ptr := C.malloc(C.size_t(100)); C.free(unsafe.Pointer(&ptr))——这里取了&ptr(即**C.char),而不是ptr本身 - 安全写法:始终确保
unsafe.Pointer参数直接来自C分配函数返回值,中间不经过取地址、数组索引或结构体字段偏移 - 调试技巧:用
go run -gcflags="-m" main.go确认没有意外逃逸;用CGO_CFLAGS=-D_GLIBCXX_DEBUG编译C部分(如适用)可提前捕获越界free
第三方C库的内存管理策略必须查清文档,不能靠猜
很多C库(如SQLite、OpenSSL)有自己的一套内存管理API,比如sqlite3_free、OPENSSL_free,它们内部可能用自定义allocator、池化或直接调用系统malloc。混用C.free和这些专用free函数,轻则内存泄漏,重则崩溃。
立即学习“go语言免费学习笔记(深入)”;
- 必须查清:该库头文件里对应分配函数(如
sqlite3_malloc)的配套释放函数名是什么,是否强制要求成对使用 - 常见误区:“既然都是malloc出来的,C.free应该能通用”——实际很多库会重载
malloc,但C.free指向的是glibc的free,二者不兼容 - 稳妥做法:封装一层Go wrapper,比如
func sqliteFree(p unsafe.Pointer) { C.sqlite3_free(p) },杜绝直接碰C.free
最麻烦的不是记不住C.free怎么用,而是C侧内存生命周期藏在文档角落、函数命名模糊、甚至不同版本行为突变。每次对接新C库,第一件事不是写Go代码,是翻它的内存管理章节,盯住每个分配函数对应的释放函数,一个字母都不能错。










