The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
kkhaike

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

  •  
  •   kkhaike ·
    kkhaike · 20h 5m ago · 981 views

    最近把一个持续开发中的项目整理到了可以公开试用的阶段: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 。

    相关链接:

    6 replies    2026-08-03 19:51:53 +08:00
    ranxianglei
        1
    ranxianglei  
       20h 0m ago via Android
    支持一下,否则一会站长就给你移动到推广了 ,别问我怎么知道的
    sduoduo233
        2
    sduoduo233  
       19h 17m ago via Android
    https://gitlab.com/cznic/ccgo

    和这个比呢?这个项目也是把 c 项目转成纯 go 的
    kkhaike
        3
    kkhaike  
    OP
       18h 16m ago
    @sduoduo233 纯 go 限制有点多,之前做这个项目的目的是为了把很多 simd 移植到 go ,这个 纯 go 做不到,使用汇编的话限制就很少了。
    zoharSoul
        4
    zoharSoul  
       12h 46m ago
    感觉挺好的
    但是正文用 ai 润色后有点看不下去....
    我现在看见 不是....而是.... 就头疼
    383394544
        5
    383394544  
    PRO
       9h 31m ago
    感觉挺好的,但这个长文分段架构没啥人味 😂 在技术论坛的分享文应该短又真诚,长文可以放 README.md 让有兴趣的人慢慢看。
    383394544
        6
    383394544  
    PRO
       9h 30m ago
    不是在说层主用 AI 写文章,只是觉得很多人现在行文风格也被 AI 文带坏了。
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   938 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 38ms · UTC 21:22 · PVG 05:22 · LAX 14:22 · JFK 17:22
    ♥ Do have faith in what you're doing.