
使用 cgo 时,不能直接返回 Go 分配的 []*C.char,因为 Go 运行时禁止将 Go 堆指针传递给 C;正确做法是用 C.malloc 在 C 堆上分配连续的 **C.char 内存,并逐个复制 C.CString,最后返回裸指针。
使用 cgo 时,不能直接返回 go 分配的 `[]*c.char`,因为 go 运行时禁止将 go 堆指针传递给 c;正确做法是用 `c.malloc` 在 c 堆上分配连续的 `**c.char` 内存,并逐个复制 `c.cstring`,最后返回裸指针。
在 Go 中通过 cgo 导出函数供 C 调用时,若需返回字符串数组(即 C 中的 char**),必须严格遵守内存所有权规则:所有由 Go 函数返回给 C 的指针,其指向的内存必须完全分配在 C 堆上。否则会触发 runtime error: cgo result has Go pointer panic —— 这是 Go 的安全机制,防止 C 持有对 Go 堆内存的引用(可能导致 GC 错误或悬垂指针)。
以下是一个完整、安全的实现示例(含必要导入与内存管理说明):
package csplit
/*
#include <stdlib.h>
#include <string.h>
*/
import "C"
import (
"strings"
"unsafe"
)
//export Split
// Split splits a C string by a delimiter and returns a C-allocated array of C strings.
// Caller (C code) is responsible for freeing each string with C.free() and the array itself.
func Split(original *C.char, split *C.char) **C.char {
if original == nil || split == nil {
return nil
}
goOriginal := C.GoString(original)
goSplit := C.GoString(split)
goResult := strings.Split(goOriginal, goSplit)
n := len(goResult)
if n == 0 {
// Return null pointer for empty result
return nil
}
// Allocate C heap memory for an array of *C.char (i.e., char**)
// Each element is a pointer (uintptr-sized), so size = n * sizeof(char*)
elemSize := unsafe.Sizeof((*C.char)(nil))
cArray := C.malloc(C.size_t(n) * C.size_t(elemSize))
// Convert raw memory to a Go slice for safe indexing
a := unsafe.Slice((**C.char)(cArray), n)
// Populate with individually allocated C strings
for i, s := range goResult {
a[i] = C.CString(s) // Each CString is C-allocated and must be freed separately
}
return (**C.char)(cArray)
}✅ 关键要点说明:
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- 使用
C.malloc分配**C.char数组(即char**),而非 Go 切片; - 用
unsafe.Slice将unsafe.Pointer安全转为可索引的[]*C.char视图; - 每个子字符串调用
C.CString()单独分配,确保每个*C.char都来自 C 堆; -
重要:C 端调用者必须负责释放资源 —— 先遍历数组对每个
*C.char调用free(),再对**C.char数组本身调用free();Go 不会自动回收这些内存。
⚠️ 注意事项:
- 此函数不处理空输入或空结果边界情况(已添加基础 nil 检查,生产环境建议增强错误处理);
-
C.CString分配的内存不可被 Go 修改(如后续C.free()后再访问将导致未定义行为); - 若需支持空字符串分隔或保留空字段,请在
strings.Split后显式过滤或使用strings.FieldsFunc等替代方案。
该模式是 cgo 中跨语言字符串数组交互的标准实践,兼顾安全性、兼容性与明确的所有权语义。

















