使用 C2Go
Managed libc(mlib)
使用直接对 GC 可见的指针调用有状态 libc API,并明确管理 managed 所有权。维护者C2Go contributors根 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。