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

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

使用 C2Go

Managed libc(mlib)

使用直接对 GC 可见的指针调用有状态 libc API,并明确管理 managed 所有权。

根 c2go_libc 包保留普通 C 风格的 unmanaged carrier。这些 carrier 需要长期保存 Go 状态时,通常只保存整数 handle,再通过 Go 侧表查找真正对象。mlib 是给已经采用 C2Go managed memory 的代码使用的另一套接口:carrier 直接保存 GC 可见的 Go 指针。

当 libc 对象需要成为 managed 对象图的一部分时使用 mlib。它不是每个 libc 函数的“更快别名”,mlib carrier 也绝不能传给对应的 root libc 函数。

一套行为,两种存储世界

mlib 不会把完整 musl 编译两遍:

  • 无状态算法直接共享;
  • Go 持有的行为核心直接共享,root 侧包一层 handle,mlib 侧包一层直接指针;
  • 只有 record layout、allocator、指针复制或返回对象图必须使用 managed storage 时,才额外实例化对应 C 代码。

例如 semaphore 和 pthread 同步对象共享同一套 Go 行为核心;managed regex 的 allocator 和所有权图不同,因此选择性实例化第二套 TRE。这样既不破坏 root libc ABI,也不会把 managed 指针藏进 GC 不扫描的 C 内存。

只包含实际使用的函数簇头文件

当前刻意不提供 mlib.h umbrella header。请按函数簇包含真正拥有声明的头文件:

#include <c2go.h>
#include <c2go/mlib/semaphore.h>
#include <c2go/mlib/pthread.h>
#include <c2go/mlib/dirent.h>
#include <c2go/mlib/glob.h>
#include <c2go/mlib/regex.h>
#include <c2go/mlib/search.h>
#include <c2go/mlib/stdio.h>
#include <c2go/mlib/string.h>
#include <c2go/mlib/stdlib.h>
#include <c2go/mlib/unistd.h>
#include <c2go/mlib/wchar.h>

默认接口带明确命名空间,例如 mlib_sem_t、mlib_sem_init、mlib_fopen。这种模式可以与对应的普通 libc 头文件同时使用。

使用类型化 carrier,并清除两层 owner

在 Go heap 上分配包含指针的 carrier 时,必须提供真实类型信息:

#include <c2go.h>
#include <c2go/mlib/semaphore.h>

#pragma c2go managed push

struct managed Owner {
    mlib_sem_t *sem;
};

static struct Owner *new_owner(void) {
    struct Owner *owner = gc_malloc(
        c2go_typeinfo(struct Owner), sizeof(*owner));
    if (owner == NULL) return NULL;

    owner->sem = gc_malloc(
        c2go_typeinfo(mlib_sem_t), sizeof(*owner->sem));
    if (owner->sem == NULL || mlib_sem_init(owner->sem, 0, 1) != 0) {
        owner->sem = NULL;
        return owner;
    }
    return owner;
}

static void release_owner(struct Owner *owner) {
    if (owner != NULL && owner->sem != NULL) {
        mlib_sem_destroy(owner->sem); /* 清除 carrier 内的状态 root。 */
        owner->sem = NULL;            /* 清除调用方持有的 carrier root。 */
    }
}

#pragma c2go pop

不可达存储最终由 GC 回收,但逻辑释放仍然必须明确。mlib 中形如 destroy、close、delete、free 的操作会清除所有能够触达的库内 managed root。若 POSIX 签名只能按值接收 descriptor,它无法改写调用方变量;调用方仍应把最后一个 owner 显式置为 NULL。

不要对 mlib storage 调用普通 free。mlib 不提供、也不使用 malloc、realloc 或 free。包含指针的 record 使用带类型的 gc_malloc;只有确定永远不含 managed 指针的存储才能使用 gc_malloc(NULL, size)。

可选的标准源码函数名

如果整个 LTO package 都应使用标准源码拼写,请在第一个 mlib 头文件之前定义 C2GO_MLIB_UNPREFIXED:

#define C2GO_MLIB_UNPREFIXED 1
#include <c2go/mlib/semaphore.h>

#pragma c2go managed push
static void example(void) {
    sem_t sem;
    sem_init(&sem, 0, 1);
    sem_destroy(&sem);
}
#pragma c2go pop

该宏只改变公开名称,不改变所有权规则。mlib 头文件必须先于对应的普通 libc 头文件;同一个 LTO package 不能混合定义同一标准 API 的 managed 和 unmanaged 版本。逐步迁移时,默认的 namespaced 模式更安全。

当前 managed 函数簇

函数簇 当前提供的 managed 所有权
同步 unnamed semaphore;pthread 生命周期、key、mutex、condition variable、rwlock
文件系统对象图 DIR、scandir、Unix nftw/ftw、glob
流 managed FILE、标准流、格式化/宽字符 I/O、内存/cookie 流、popen/pclose
搜索容器 使用类型化指针写入的 AVL tree、hash table、linked queue
POSIX regex 使用 per-object GC arena 的 regcomp、regexec、regfree
分配型 helper strdup、strndup、wcsdup、asprintf、vasprintf、realpath、getcwd

这仍是一套选择性接口。iconv 保留在 root libc,因为精确的 POSIX iconv_t 错误值语义无法放进 precise managed pointer slot。lsearch 和 lfind 只有 void * 与元素宽度,无法推导 pointer bitmap,因此仍是仅适用于无指针元素的 root 函数。

使用 managed allocation 并不会消除 C2Go 的栈逃逸限制。库或 callback 不能在 C 栈 frame 返回后继续保存该 frame 中 local 的地址;请改用类型化 heap carrier,并对完整 package 运行栈指针逃逸审计。

当所有 translation unit 已采用一致的所有权模式后,继续阅读多文件项目与 LTO。