分享一个还在 RC 阶段的项目:把 C 编译成不依赖 cgo 的 Go package

kkhaike 2026-08-03 09:16 1

最近把一个持续开发中的项目整理到了可以公开试用的阶段:C2Go Toolchain


项目地址:https://github.com/c2gohq/c2go_toolchain


中文文档:https://c2go.buymecompile.top/zh-cn/


当前预发布版本:v0.20260802.0-rc.1


先用一句话说明它在做什么:



C2Go 基于 Clang/LLVM ,把 C 源码提前编译成可由 Go 工具链直接构建的 .go + .s package ;生成结果不使用 import "C",使用方可以在 CGO_ENABLED=0、没有宿主 C 编译器的环境中完成构建。



它不是把 C 文本逐句改写成 Go 源码,也不是在应用启动后嵌入一个 C 编译器。C 的编译发生在生成阶段,最终产物进入普通的 Go build 流程。


为什么做这个项目


Go 调用已有 C 实现时,最成熟的方案当然还是 cgo 。但我想探索的是另一条路径:



  • 能不能继续使用 Clang/LLVM 的 C 前端和优化能力;

  • 能不能把结果变成 Go package ,而不是在最终构建时再次调用 C toolchain ;

  • 能不能让生成代码运行在 Go runtime 下,使用 goroutine 栈、Go ABI 和 GC 可见的内存;

  • 能不能让库的最终使用者在 CGO_ENABLED=0 的环境中构建。


C2Go 就是围绕这些目标做的一套编译器、绑定生成器和 libc 兼容层。


它具体生成什么


目前的管线大致如下:


C 源码


c2go-clang + c2go-lto
│ Plan 9 汇编 + JSON manifest

c2go-bind
│ 生成 .go + .s

普通 Go package


go build / go test (可使用 CGO_ENABLED=0 )

几个主要仓库的分工是:



  • c2go-clang:基于 LLVM/Clang 的 C2Go 编译模式和 LTO 工具;

  • c2go-bind:读取汇编与 manifest ,生成 Go 声明、ABI glue 和 package 文件;

  • c2go-libc:面向生成代码的 libc/runtime 兼容层,并使用 PureGo 处理需要的原生调用边界;

  • c2go-toolchain:锁定各组件版本并统一发布 SDK 。


一个最小例子


先下载与当前系统匹配的 SDK 、把 bin 加入 PATH。下面以 macOS arm64 为例,其他平台只需要换成对应的 target triple 。


mkdir hello-c2go
cd hello-c2go
mkdir .c2go

go mod init example.com/hello-c2go
go get github.com/c2gohq/[email protected]

export C2GO_TARGET=aarch64-apple-darwin

创建 .c2go/input.c


#include <stdint.h>
#include <c2go.h>

c2go_extern int add(int a, int b) {
return a + b;
}

编译 C ,并生成汇编和 manifest:


c2go-clang --target="$C2GO_TARGET" \
-fc2go \
-fc2go-package=example.com/hello-c2go/translated \
-O2 \
-fc2go-emit-plan9-asm=.c2go/translated.s \
-fc2go-emit-manifest=.c2go/translated.json \
.c2go/input.c

生成 Go package:


mkdir -p translated

c2go-bind \
--out=translated \
--sidecar=.c2go/translated.json \
.c2go/translated.s

创建 main.go


package main

import (
"fmt"

"example.com/hello-c2go/translated"
)

func main() {
fmt.Println(translated.Add(20, 22))
}

现在可以直接交给 Go 工具链:


CGO_ENABLED=0 go run .

输出:


42

这里的 c2go_extern 表示把已经定义的 C 函数导出给 Go 。C 的 int 仍然是 32 位,因此生成的实际签名是 func Add(a, b int32) int32;示例中的无类型常量可以直接传入。


和普通 C 不太一样的地方


C2Go 需要同时面对 C 的内存模型和 Go runtime ,因此明确区分 managed 与 unmanaged 数据:



  • managed 对象可以带有精确的 GC 类型信息,并分配在 Go heap ;

  • unmanaged 对象用于原生 C 内存、系统库或其他 C ABI 边界;

  • gc_mallocc2go_typeinfoc2go_linkname、callback/callout 等扩展用来描述两边的类型、所有权和调用关系。


这部分不是为了创造一门全新的 C 方言,而是把原本藏在 cgo glue 或人工约定里的边界信息明确交给编译器检查。


对普通使用场景,可以在需要 Go managed 语义的区域使用:


#pragma c2go managed push

/* managed C code */

#pragma c2go managed pop

无参数的 managed 默认启用全部 managed 能力;只有需要精细控制时才使用更具体的属性。


目前做到什么程度了


v0.20260802.0-rc.1 已发布以下四种 SDK:































系统 架构 Target triple
Linux amd64 x86_64-unknown-linux-goabi
Linux arm64 aarch64-unknown-linux-goabi
Windows amd64 x86_64-pc-windows-goabi
macOS arm64 aarch64-apple-darwin

当前版本对应 Go 1.25.x 。项目已经用一份经过适配的 musl 生成 c2go-libc ,并持续用 Go 测试覆盖字符串、内存、数学、文件 I/O 、动态加载等路径。


这仍然是 pre-1.0 的 RC 版本,更适合尝鲜、移植实验和帮助发现边界问题,暂时不建议不经审计就直接用于生产系统。


先把最重要的限制讲清楚


当前 C2Go 不会像 Go 编译器一样,把发生逃逸的 C 栈局部变量自动提升到堆


如果一个局部变量的地址被保存进堆对象、全局变量、异步任务或其他会长期持有它的状态,那么函数返回或 goroutine 栈移动后,这个地址可能失效。同步调用链中,在对象生命周期内传递地址且 callee 不保留它,并不等于逃逸;问题在于指针是否可能比所属栈对象活得更久。


因此,准备发布的代码需要把独立的 c2go-lto 全程序逃逸审计设成门禁;确实需要长期保留的数据,应按所有权放入带正确类型信息的 Go heap ,或放入相应 native allocator 管理的内存。普通编译成功本身不能替代这项审计。


此外,当前还有这些明确边界:



  • 只支持 C 和 64 位目标,不支持 C++;

  • VLA 和动态 alloca 会被拒绝;

  • 一些复杂 record 、callback 和多返回值 ABI 形状会 fail closed ;

  • Go 1.26 暂不在当前 ABI 兼容窗口;

  • macOS 和 Windows 的预发布二进制目前尚未签名。


完整说明在平台与当前限制和栈指针逃逸审计两章。


开源协议



  • c2go-clang 延续 LLVM 的 Apache-2.0 WITH LLVM-exception

  • c2go-bind 的原创部分使用 AGPL-3.0-only,也可以另行取得商业许可;

  • c2go-libc 同时包含 C2Go 原创代码、musl 派生代码和其他第三方材料,各部分保留各自协议与声明。


具体权利边界以每个仓库中的 LICENSENOTICE 和文件头为准。


想听听 V2EX 各位的意见


目前我最想收集的不是“它能不能替代所有 cgo”——现阶段答案显然是否定的——而是真实 C 项目带来的问题:



  • 哪些 Makefile/CMake 构建方式最需要更顺滑的接入;

  • 哪些 callback 、struct 、函数指针或系统库边界最容易卡住;

  • 目前的命令行和诊断是否足够容易理解;

  • 文档中哪些概念对有 C 经验、但不了解 Go ABI 的人仍然太绕。


如果你手上有一个体量适中、测试比较完整的纯 C 库,也欢迎拿它试一下。遇到编译、ABI 或文档问题,可以直接在对应仓库提 issue 。


相关链接:



  • 官网与中文文档:https://c2go.buymecompile.top/zh-cn/

  • Hello World:https://c2go.buymecompile.top/zh-cn/docs/hello-world/

  • GitHub 组织:https://github.com/c2gohq

  • Toolchain Release:https://github.com/c2gohq/c2go_toolchain/releases/tag/v0.20260802.0-rc.1

最新回复 (4)
  • ranxianglei 08-03 09:21
    1
    支持一下,否则一会站长就给你移动到推广了 ,别问我怎么知道的
  • sduoduo233 08-03 10:04
    2
    https://gitlab.com/cznic/ccgo

    和这个比呢?这个项目也是把 c 项目转成纯 go 的
  • kkhaike 楼主 08-03 11:05
    3
    @sduoduo233 纯 go 限制有点多,之前做这个项目的目的是为了把很多 simd 移植到 go ,这个 纯 go 做不到,使用汇编的话限制就很少了。
  • zoharSoul 08-03 16:35
    4
    感觉挺好的
    但是正文用 ai 润色后有点看不下去....
    我现在看见 不是....而是.... 就头疼
* 帖子来源V2EX
返回