搜索文档搜索当前语言
使用 C2Go
候选版本v0.20260809.0-rc.5

本文档对应当前协同发布候选版本。

使用 C2Go

内存与 Go GC

判断一块 C 数据应由 Go GC 管理,还是继续使用普通 C 生命周期。

C 程序通常自己决定内存何时释放;Go 则由垃圾回收器决定对象何时可以回收。C2Go 需要知道某个 C 指针属于哪一种生命周期,否则 Go GC 可能看不到仍在使用的对象。

这里的 managed/unmanaged 描述的是 GC 如何看待指针,不是一套自动 ownership 系统;谁创建、谁释放、callee 是否保留指针,仍然需要由 API 明确约定。

阅读本章时只需要先回答两个问题:

  1. 这块内存由 Go GC 回收,还是由代码、系统或原生库释放?
  2. 这块内存内部是否保存了需要被 Go GC 继续跟踪的指针?

两种内存的直观区别

使用场景 选择 原因
使用 gc_malloc 创建、需要跨 Go 调用继续存活的对象 managed Go GC 必须知道对象类型和内部指针位置
Go 管理的链表、树或其他含指针对象 managed 写入指针时需要 Go write barrier
普通 malloc/free 缓冲区 unmanaged 生命周期仍由 C 代码手动控制
系统调用、动态库或第三方 C ABI 使用的结构体 unmanaged 必须保持原生 C 内存与 ABI 语义
函数中的普通局部变量 保持默认 只能在当前调用期间使用,不能把地址长期保存

当前 C2Go 默认使用普通 C 的 unmanaged 语义。只有确实需要 Go GC 跟踪的数据才应显式标为 managed。

需要 Go GC 管理的对象

下面的链表节点会保存另一个节点的指针,并且可能跨多次 Go/C 调用存活,因此将其声明为 managed:

#include <c2go.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(*node));
    node->next = 0;
    node->value = value;
    return node;
}

这里有三件事协同工作:

  • managed 告诉编译器节点中哪些字段是 GC 指针;
  • c2go_typeinfo(struct Node) 提供该类型的布局;
  • gc_malloc 在 Go heap 上创建这个带类型的对象。

给 node->next 赋值时,编译器会生成 Go runtime 需要的 write barrier。仅仅写上 managed 不会分配内存,也不会把栈变量自动搬到堆上。

保持普通 C 生命周期

传给操作系统、动态库或现有 C API 的数据通常应保持 unmanaged:

#include <c2go.h>

struct unmanaged NativeBuffer {
    void *data;
    size_t length;
};

extern int vendor_write(struct NativeBuffer *buffer);

普通 malloc、calloc、realloc 和 free 也保留手动释放语义。不要在这类裸缓冲区中保存需要 Go GC 跟踪的 managed 指针;GC 不知道缓冲区内部的字段布局。

像 vendor_write 这样只有声明、没有定义的函数已经是 native import,不需要显式 unmanaged 标记。

unmanaged 可以在接口边界上显式表达意图。对于没有标记的普通 C 类型,它也是当前默认行为。

最常见的错误

把局部变量地址长期保存

void attach(struct Node *node) {
    struct Node local;
    node->next = &local; // local 会随函数返回而失效
}

Go 不会像 Go 编译器处理 Go 源码那样,自动把这个 C 局部变量提升到 heap。源码诊断只能覆盖一部分直接写法,发布前还必须运行栈指针逃逸审计。

混合两种内存而不说明转换

把 managed 指针写入 unmanaged 存储、转换成整数,或通过不兼容布局的类型访问,都可能让 GC 丢失指针信息。C2Go 会对此产生 warning 或 error。不要通过整数中转或关闭 warning 来绕过它;应重新设计边界,或者把数据复制到生命周期清晰的表示中。

让 GC 无法识别字段位置

包含 GC 指针的布局不能随意 packing、重叠或改变 alignment。Managed record 的特殊布局会产生警告;无法安全扫描的 unmanaged record 会被拒绝。公开此类类型前,应检查生成的 Go 类型并测试每个 target。

实际选择原则

先保留普通 C 写法。只有当对象同时满足“由 Go heap 持有”或“内部指针必须由 Go GC 跟踪”时,才引入 managed 类型和 gc_malloc。与操作系统、动态库和原生 ABI 交互的内存继续保持 unmanaged,并维持明确的创建与释放关系。

属性、pragma bitmask 和转换诊断的完整语法放在 C2Go 扩展速查;下一章用 GC 感知分配构造更完整的 managed 对象。