You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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。

具体修复步骤

  1. 固定cStrings2切片:cDataPtr从&cStrings2[0]转换而来,该指针指向的是cStrings2的底层数组(Go内存),必须用Pinner固定它。
  2. 调整固定顺序:确保在创建依赖指针前完成固定操作,避免指针失效。

修复后的完整代码:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 03:35:07