不能直接传 &s[0],因为C函数无内存边界保护,越界写入会破坏相邻内存或因GC移动导致地址失效;安全做法是用C.malloc分配内存,映射为Go切片,C函数写入后显式调用C.free。

Go字节切片接收C函数写入时,为什么不能直接传 &s[0]?
因为 &s[0] 获取的是切片底层数组首元素地址——这本身没问题;但问题出在**C函数写入时缺乏内存边界保护**。Go切片的 len 和 cap 对C代码完全不可见,C端若越界写入(比如循环写满1024字节但Go切片只有512容量),会直接破坏相邻内存,触发崩溃或静默数据损坏。更隐蔽的风险是:若该切片来自 make([]byte, n) 且后续被GC“收缩”(如逃逸分析失败导致栈分配失败转堆分配,再被移动),C写入地址就彻底失效。
安全做法:用 C.malloc 分配内存,再映射为 Go 切片
核心原则是让C和Go共用同一块由C分配、生命周期可控的内存。Go侧只负责构造切片视图,不参与分配/释放决策。
- 调用
C.malloc(size_t(n))获取裸内存指针p - 用
unsafe.Slice((*byte)(p), n)(Go 1.23+)或reflect.SliceHeader手动构造切片(旧版本) - 把该切片传给C函数,让它直接写入
- 使用完毕后,必须显式调用
C.free(p),且确保无其他goroutine正在读写该切片
示例关键片段:
// 分配
n := 1024
p := C.malloc(C.size_t(n))
defer C.free(p) // 注意:必须 defer 在使用前
// 映射为 []byte(Go 1.23+)
buf := unsafe.Slice((*byte)(p), n)
// 传给C函数(假设它接受 *C.char 和 length)
C.fill_buffer((*C.char)(p), C.int(n))
// 此时 buf 已被C写入,可直接使用
fmt.Printf("first byte: %d", buf[0])
为什么不用 C.CString 或 C.CBytes?
C.CString 强制添加 \0 终止符,且只适用于输入场景(C读取),不支持C写入;C.CBytes 返回的是新分配的C内存副本,但返回值是 *C.uchar,**没有长度信息**,无法安全转回Go切片——你不知道它实际写了多少字节,也无法控制写入上限。二者都不满足“C函数向Go缓冲区写入”的双向交互需求。
立即学习“go语言免费学习笔记(深入)”;
容易被忽略的生命周期陷阱
最常踩的坑是:把C分配的内存映射成Go切片后,又把它传进 channel 或 goroutine 异步处理,却忘了 C.free 的调用时机。一旦 C.free 提前执行,所有基于该切片的读写都变成悬空指针访问。反过来,若忘记 C.free,则造成C堆内存泄漏——Go的GC对此完全无感知。务必确保:free 调用发生在所有对该切片的引用结束后,且仅发生一次。


















