将Go指针数组传递给C的Golang实现方法
将Go指针数组传递给C的Golang实现方法 根据 cgo 规则 § 传递指针:
Go 代码可以将 Go 指针传递给 C,前提是该指针所指向的 Go 内存不包含任何 Go 指针。
因此,像下面这样的代码是不允许的:
func test_slices(slices ...[]byte) int {
lens := make([]C.size_t, len(slices))
ptrs := make([]*C.uchar, len(slices))
for i, _ := range slices {
lens[i] = (C.size_t)(len(slices[i]))
ptrs[i] = (*C.uchar)(&slices[i][0])
}
cPtrInputs := (**C.uchar)(&ptrs[0])
cSizesPtr := (*C.size_t)(&lens[0])
cAmounts := (C.size_t)(len(slices))
C.test_slices(cPtrInputs, cSizesPtr, cAmounts)
}
这是为什么呢?为什么我们可以将 Go 指针传递给 C,却不能将一个 Go 指针数组传递给 C?
另外,正确的做法是什么?通过 C.malloc 分配 C 指针 + 复制 + 传递这些指针吗?有没有一种方法可以避免 malloc + copy 的开销?
更多关于将Go指针数组传递给C的Golang实现方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我认为来自 cgoCheckPointer 的这段源代码暗示了原因:
// When and if we implement a moving garbage collector,
// cgoCheckPointer will pin the pointer for the duration of the cgo
// call. (This is necessary but not sufficient; the cgo program will
// also have to change to pin Go pointers that cannot point to Go
// pointers.)
如果允许传递指向指针的指针,那么当/如果 Go 开始移动其内存分配时,尽管切片本身被固定了,但切片内的任何指针元素都可能在任何时候被移动,cgo 最终可能会访问到已释放/已被重新利用的内存。为了防止这种情况,cgoCheckPointer 将不得不做一些事情,比如递归地固定整个指针到指针到指针……的图,对于使用 cgo 的程序完全禁用内存移动,或许在 cgo 调用期间禁用移动,等等。
更多关于将Go指针数组传递给C的Golang实现方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
根据 cgo 规则,传递包含 Go 指针的指针数组确实违反规则。原因在于 C 代码可能存储这些指针并在后续 Go 内存管理(如垃圾回收)期间访问它们,而 Go 运行时无法追踪 C 中存储的 Go 指针。
以下是正确的实现方法:
package main
/*
#include <stdlib.h>
void process_slices(unsigned char** ptrs, size_t* lens, size_t count) {
// C 函数处理切片数据
}
*/
import "C"
import (
"unsafe"
)
func processGoSlices(slices [][]byte) {
if len(slices) == 0 {
return
}
// 分配 C 内存存储指针和长度
cPtrs := (**C.uchar)(C.malloc(C.size_t(len(slices)) * C.size_t(unsafe.Sizeof(uintptr(0)))))
cLens := (*C.size_t)(C.malloc(C.size_t(len(slices)) * C.size_t(unsafe.Sizeof(C.size_t(0)))))
// 获取底层数组指针
ptrSlice := (*[1 << 30]*C.uchar)(unsafe.Pointer(cPtrs))[:len(slices):len(slices)]
lenSlice := (*[1 << 30]C.size_t)(unsafe.Pointer(cLens))[:len(slices):len(slices)]
// 填充数据
for i := range slices {
if len(slices[i]) > 0 {
ptrSlice[i] = (*C.uchar)(unsafe.Pointer(&slices[i][0]))
} else {
ptrSlice[i] = nil
}
lenSlice[i] = C.size_t(len(slices[i]))
}
// 调用 C 函数
C.process_slices(cPtrs, cLens, C.size_t(len(slices)))
// 释放 C 内存
C.free(unsafe.Pointer(cPtrs))
C.free(unsafe.Pointer(cLens))
}
如果希望避免复制数据,可以使用以下方法直接传递 Go 内存:
func processSlicesNoCopy(slices [][]byte) {
if len(slices) == 0 {
return
}
// 创建指向第一个元素的指针
ptrArray := make([]*C.uchar, len(slices))
for i := range slices {
if len(slices[i]) > 0 {
ptrArray[i] = (*C.uchar)(unsafe.Pointer(&slices[i][0]))
} else {
ptrArray[i] = nil
}
}
// 通过 unsafe 转换为 C 指针
cPtrs := (**C.uchar)(unsafe.Pointer(&ptrArray[0]))
cLens := make([]C.size_t, len(slices))
for i := range slices {
cLens[i] = C.size_t(len(slices[i]))
}
// 调用时确保 C 函数不会存储指针
runtime.KeepAlive(slices) // 防止垃圾回收
runtime.KeepAlive(ptrArray)
C.process_slices(cPtrs, &cLens[0], C.size_t(len(slices)))
}
关键点:
- C 代码必须同步使用指针,不能存储供后续使用
- 需要确保 Go 切片在 C 调用期间保持活跃
- 空切片需要特殊处理
对于性能敏感场景,建议使用第一种 C.malloc 方法,虽然需要额外分配,但完全符合 cgo 规则且安全。

