使用 C2Go
GC 感知分配
在 Go heap 上分配带类型的 C 对象,并保留准确的指针布局。维护者C2Go contributorsgc_malloc 会在 Go heap 上分配清零内存。与普通 C 分配不同,它接收 runtime 类型信息,让垃圾回收器知道哪些机器字保存着指针。
分配一个 record
#include <c2go.h>
#include <stddef.h>
#include <stdint.h>
struct managed Node {
struct Node *next;
int value;
};
struct Node *new_node(int value) {
struct Node *node = gc_malloc(
c2go_typeinfo(struct Node),
sizeof(struct Node)
);
if (node == NULL) {
return NULL;
}
node->value = value;
return node;
}
c2go_typeinfo(struct Node) 是通过宏公开的编译器 builtin,返回这个具名 managed record 对应的 Go runtime 类型描述符。无需手写或注册 pointer bitmap。
它没有对应的 free;对象不可达后由 Go GC 回收。
当数据必须活过当前 C 栈帧时,也应把它改写为 heap 分配。不要把 &local 存进这个对象;C2Go 不会自动把 local 移动到堆。详见栈指针逃逸安全。
分配数组
当每个元素都需要重复相同指针元数据时,使用 gc_malloc_array:
struct Node *nodes = gc_malloc_array(
c2go_typeinfo(struct Node),
sizeof(struct Node),
count
);
runtime 会在整块内存中重复元素的 pointer bitmap,类似为 Go slice 分配 backing array。
当前 inline helper 直接计算 elem_size * count,不会替调用方检测乘法溢出。处理不可信长度时,先验证 count <= SIZE_MAX / elem_size,并检查返回值是否为 NULL。
分配不含指针的 blob
把 NULL 作为类型信息会得到 GC 不扫描指针的内存:
unsigned char *bytes = gc_malloc(NULL, byte_count);
只有确认其中永远不会保存 managed 指针时才能这样做。把 managed 指针隐藏在 blob 中,可能让被引用对象在 GC 看来已经不可达。
不要混用所有权模型
| allocator | 世界 | 释放方式 |
|---|---|---|
gc_malloc / gc_malloc_array |
typed managed Go heap | 自动 GC |
malloc / calloc / realloc |
c2go-libc unmanaged C 语义 storage | free |
不要对 typed Go heap 指针调用 free,也不要假设原生库可以无限期保存 managed 指针。如果原生 API 接管所有权,应使用该 API 明确要求的 allocator 并复制数据,或设计稳定 handle 边界;c2go-libc 的 malloc 并不等同于宿主系统 malloc。
类型信息限制
c2go_typeinfo 要求具名且完整的 record 类型。匿名 record 和保留的 runtime 标识符会被拒绝。Packed 或自定义 alignment 的 managed record 会产生 warning,生成布局也与 target 相关;可移植的公开类型应优先使用自然布局。
下一章把相同规则扩展到多文件项目与 LTO。