cgo禁止传递含Go指针的Go对象给C函数,因GC无法追踪嵌套指针链;unsafe.Pointer(&slice[0])传指针数组仍违反规则,因元素*C.int本身是Go指针;正确做法是C端用C.malloc分配指针数组内存并由C管理。

cgo 不允许直接传递含 Go 指针的 Go 对象(如 []<em>C.int</em>、struct{...} 等)给 C 函数——这是硬性限制,不是风格建议。一旦违反,运行时会 panic:cgo argument has Go pointer to Go pointer。根本原因在于 Go 运行时无法追踪嵌套指针链,GC 可能移动或回收底层内存,导致 C 侧访问悬空地址。
为什么不能用 unsafe.Pointer(&slice[0]) 传指针数组?
常见错误是把 Go 分配的 []*C.int 切片首地址转成 unsafe.Pointer 传给 C:C.foo((*C.pInt)(unsafe.Pointer(&ps[0])))。这看似“只是传个地址”,但 ps 是 Go 管理的切片,其元素本身是 Go 指针(指向 C.int),Go 运行时禁止将“Go 指针指向 Go 指针”的结构暴露给 C。
-
ps的底层数组由 Go 分配,元素类型是*C.int—— 即每个元素都是一个 Go 指针 -
&ps[0]是指向第一个 Go 指针的地址,仍属 Go 内存,且该指针值本身又指向另一块内存(C 分配的C.int) - C 函数接收后若长期持有或跨调用使用,GC 无法保证
ps或其元素不被回收或移动
正确做法:C 端分配指针数组内存
必须把“指针数组”这块内存完全交给 C 管理,Go 只负责填值、传地址、最后释放。这样 C 函数拿到的是纯 C 内存中的连续 C.pInt(即 *C.int)序列,无嵌套 Go 指针。
- 用
C.malloc分配足够容纳n个*C.int的内存:ptrArray := (*C.pInt)(C.malloc(C.size_t(n) * C.size_t(unsafe.Sizeof((*C.int)(nil))))) - 逐个写入原始指针值:
(*(*[1 (注意:不是赋 Go 指针,而是赋 <code>C.int地址) - 传给 C 函数:
C.foo(ptrArray) - 用完后必须
C.free(unsafe.Pointer(ptrArray)),否则内存泄漏
字符串切片 []string → char** 的等价处理
这是最典型场景,原理相同:不能传 []*C.char 的 Go 切片地址,而要让 C 端管理指针数组。
立即学习“go语言免费学习笔记(深入)”;
- 先为每个
string调用C.CString,得到独立的*C.char(C 堆内存) - 再用
C.malloc分配n个*C.char大小的内存块 - 把每个
*C.char的值(不是 Go 指针!)拷贝进该块:(*(*[1 - 传
cPtrArray给 C 函数,它会被当char**使用 - 所有
C.CString结果和cPtrArray都需分别C.free
容易被忽略的生命周期细节
即使按上述方式做了 C 端分配,仍可能出错:C 函数若缓存了传入的指针(比如存全局变量、回调中复用),而 Go 侧提前 C.free 了对应内存,就会导致 C 侧访问已释放内存。真正的安全边界只有一条:
- C 函数必须明确文档化是否“持有”传入指针;若持有,Go 侧绝不能在 C 函数返回后立即释放
- 没有显式约定时,默认按“仅本次调用有效”处理,释放时机必须严格卡在 C 函数返回之后、且确认不再需要这些指针
- 涉及回调(C 调 Go)时,更不能传任何 Go 分配的指针——包括
C.CString返回的地址,除非你在回调里立刻复制数据并C.free


















