
Go 调用 C 函数时,若需让 C 写入字符串,不能依赖 sizeof(out) 获取缓冲区大小(因其返回指针长度),而应显式传入预分配的足够容量,并确保 C 端使用该容量进行安全写入。
go 调用 c 函数时,若需让 c 写入字符串,不能依赖 `sizeof(out)` 获取缓冲区大小(因其返回指针长度),而应显式传入预分配的足够容量,并确保 c 端使用该容量进行安全写入。
在 Go 中通过 C.CString("") 创建的 C 字符串,底层是调用 malloc 分配的一块以 \0 结尾的内存,但其实际容量仅够容纳空字符串(即 1 字节)。当你将其指针传给 C 函数并执行 snprintf(out, sizeof out, "...") 时,sizeof out 计算的是 char * 指针本身的大小(64 位系统为 8 字节),而非所指向内存的可用空间——这导致 snprintf 最多只写入 7 字符 + 1 字节终止符,造成截断(如 "1234567")。
正确做法是:由 Go 显式分配具备足够容量的 C 字符数组,并将指针与长度一并传入 C 函数。推荐两种主流方案:
✅ 方案一:Go 分配固定大小缓冲区(推荐,安全可控)
package main
/*
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
// 接收缓冲区指针及最大长度(含终止符)
int test(char *out, size_t max_len) {
if (max_len == 0) return -1;
// 安全写入:最多写入 max_len-1 字符 + \0
snprintf(out, max_len, "1234567890abcd");
return 0;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
const bufSize = 32 // 预留充足空间(远超预期字符串长度)
// 分配 C 字符数组:bufSize 个 char,初始化为 0
buf := C.CBytes(make([]byte, bufSize))
defer C.free(buf)
// 传入指针和真实容量(注意:C.size_t 类型)
ret := C.test((*C.char)(buf), C.size_t(bufSize))
if ret != 0 {
panic("C test function failed")
}
// 转为 Go 字符串(自动截断到首个 \0)
result := C.GoString((*C.char)(buf))
fmt.Println("Output is:", result) // 输出:1234567890abcd
}</stdio.h></string.h></stdlib.h>
⚠️ 注意事项
- C.CString("") 仅分配 1 字节,绝不可用于接收未知长度输出;
- C.CBytes([]byte{}) 分配原始字节数组,更灵活且容量明确;
- C 函数中必须使用传入的 max_len 做边界检查(如 snprintf 的第二个参数),避免缓冲区溢出;
- 若需动态长度,可在 C 中先计算所需长度(如 strlen 或 snprintf(NULL, 0, ...)),再由 Go 重新分配——但会增加跨语言调用开销,一般静态预分配更高效。
✅ 方案二:C 端动态分配(需 Go 承担释放责任)
// C 端返回新分配的字符串(需 Go 调用 C.free)
char* test_alloc() {
char *s = malloc(16);
strcpy(s, "1234567890abcd");
return s;
}
Go 端需手动管理内存:
ptr := C.test_alloc()
if ptr != nil {
s := C.GoString(ptr)
C.free(unsafe.Pointer(ptr)) // 必须释放!
fmt.Println(s)
}
此方式灵活性高,但增加了内存管理复杂度,易引发泄漏,非必要不推荐。
综上,优先采用方案一:Go 预分配确定大小的 C.CBytes 缓冲区,C 函数严格按传入长度写入——兼顾安全性、性能与可维护性。











