Go调用FFI时内存固定报错:cgo参数含未固定Go指针
解决CGO中"cgo argument has Go pointer to unpinned Go pointer" panic问题
问题背景
正在学习Go、CGO与FFI技术,现有如下C头文件:
#include <stdarg.h> #include <stdbool.h> #include <stdint.h> #include <stdlib.h> typedef struct MyStruct3 { const char *const *data; int length; } MyStruct3; void my_struct_3(const struct MyStruct3 *c_data);
对应的Go代码:
package main /* #cgo LDFLAGS: -L./release -lrust_ffi #cgo CFLAGS: -I./target #include <rust-ffi.h> */ import "C" import ( "fmt" "unsafe" ) func main() { type MyStruct3 struct { data **C.char length C.int } goStrings2 := []string{"Hello", "World", "from", "Go", "again"} pnr := new(runtime.Pinner) cStrings2 := make([]*C.char, len(goStrings2)) for i, s := range goStrings2 { cString := C.CString(s) cStrings2[i] = cString // pnr.Pin(cString) defer C.free(unsafe.Pointer(cStrings2[i])) } cDataPtr := (**C.char)(unsafe.Pointer(&cStrings2[0])) // pnr.Pin(&goStrings2) pnr.Pin(&cDataPtr) // cLength := C.int(len(goStrings2)) // pnr.Pin(&cLength) cStruct3 := MyStruct3{ data: cDataPtr, length: C.int(len(goStrings2)), } cStruct3prt := (*C.struct_MyStruct3)(unsafe.Pointer(&cStruct3)) pnr.Pin(&cStruct3) C.my_struct_3(cStruct3prt) pnr.Unpin() }
运行时触发panic:
panic: runtime error: cgo argument has Go pointer to unpinned Go pointer
使用的Go版本:
$ go version go version go1.21.5 linux/amd64
问题原因与解决方法
这个panic源于Go的cgo指针安全规则:当传递包含Go指针的结构体给C函数时,所有嵌套的Go内存指针都必须通过runtime.Pinner固定,防止Go垃圾回收器移动或释放相关内存。
你的代码中,cDataPtr指向的是Go切片cStrings2的底层数组指针,而cStrings2属于Go管理的内存,未被固定,导致结构体的data字段间接指向了未固定的Go内存,触发了安全检查panic。
具体修复步骤
- 固定
cStrings2切片:cDataPtr从&cStrings2[0]转换而来,该指针指向的是cStrings2的底层数组(Go内存),必须用Pinner固定它。 - 调整固定顺序:确保在创建依赖指针前完成固定操作,避免指针失效。
修复后的完整代码:
package main /* #cgo LDFLAGS: -L./release -lrust_ffi #cgo CFLAGS: -I./target #include <rust-ffi.h> */ import "C" import ( "runtime" "unsafe" ) func main() { type MyStruct3 struct { data **C.char length C.int } goStrings2 := []string{"Hello", "World", "from", "Go", "again"} pnr := new(runtime.Pinner) cStrings2 := make([]*C.char, len(goStrings2)) for i, s := range goStrings2 { cString := C.CString(s) cStrings2[i] = cString defer C.free(unsafe.Pointer(cStrings2[i])) } // 关键:固定cStrings2切片,确保其底层数组不被GC移动或回收 pnr.Pin(&cStrings2) cDataPtr := (**C.char)(unsafe.Pointer(&cStrings2[0])) cStruct3 := MyStruct3{ data: cDataPtr, length: C.int(len(goStrings2)), } cStruct3prt := (*C.struct_MyStruct3)(unsafe.Pointer(&cStruct3)) pnr.Pin(&cStruct3) C.my_struct_3(cStruct3prt) pnr.Unpin() }
额外说明
C.CString分配的是C堆内存,不受Go GC管理,不需要用Pinner固定,你用defer C.free处理释放的逻辑是正确的。- 固定操作必须在创建指向该内存的指针之前完成,保证指针在C函数执行期间始终有效。
- 所有被C函数访问的Go内存指针(包括嵌套的间接指针)都需要固定,直到C函数执行完毕。
内容的提问来源于stack exchange,提问作者masber
相关产品推荐
相关产品推荐

