import "C" 是 cgo 的硬性开关,必须紧贴 C 声明块;其正上方连续注释块作为 C preamble,零空行要求严格;// #cgo 指令仅在此处生效,控制 C 编译与链接;C 字符串需及时用 C.free 释放,且不可长期持有。

import "C" 不是可选操作,而是 cgo 的硬性开关——没它,Go 文件里写的任何 C 代码都不会被识别,更不会生成绑定逻辑。
为什么 import "C" 必须紧贴 C 声明块?
cgo 把 import "C" 正上方**连续的注释块**(/* */ 或 // #include 形式)当作 C 的 preamble,直接喂给 C 编译器。一旦中间插了空行、Go 代码、甚至一个多余的 // 注释,preamble 就被截断。
- 错误写法:
// #include <math.h>→ 空行 →// #include "my.h"→import "C"(第二行注释不被识别) - 正确写法:所有
#include和#cgo指令必须在同一个注释块内,且与import "C"之间**零空行** - 常见报错:
undefined reference to 'xxx'或use of undeclared identifier 'xxx',往往就卡在这一步
// #cgo CFLAGS 和 // #cgo LDFLAGS 必须写在 import "C" 前
这些指令不是 Go 注释,是 cgo 解析器专用语法,只在 import "C" 上方生效。它们控制的是 C 编译器行为,不是 Go 编译器。
-
// #cgo CFLAGS: -I./include -DMYLIB_DEBUG:影响头文件搜索路径和宏定义 -
// #cgo LDFLAGS: -L./lib -lmylib:告诉链接器去哪里找库、链接哪个名字 - 路径中可用
${SRCDIR},比如-L${SRCDIR}/csrc,避免硬编码绝对路径 - 每个
// #cgo指令独占一行,不能合并,也不能跨行
C 字符串传参后,C.free 的时机不是“用完就 free”,而是“C 函数返回后立刻 free”
C.CString 分配的是 C 堆内存,Go 的 GC 完全不管理它。但 free 太早或太晚都会出事。
立即学习“go语言免费学习笔记(深入)”;
- 最安全模式:
cstr := C.CString(s); defer C.free(unsafe.Pointer(cstr)) - 如果 C 函数内部做了
strdup或把指针存到全局/静态变量里,你就不能 free——得看 C 侧文档,不是 Go 能猜出来的 - 绝对不要把
C.CString结果存进map、全局变量或 channel:defer 失效,泄漏几乎必然 - 返回值是 C 字符串?用
C.GoString转成 Go 字符串再处理,别留着*C.char长期持有
独立 .c 文件不会自动编译,得靠 // #cgo LDFLAGS 显式链接
cgo 默认只处理 import "C" 上方注释里的 C 代码。你放一个 utils.c 在目录里,它根本不会被编译,链接时自然报 undefined reference。
- 简单方案:把 C 实现直接塞进注释块,比如
/* void foo() { ... } */ - 工程化方案:先用
gcc -c utils.c -o utils.o编译成目标文件,再用// #cgo LDFLAGS: ./utils.o链接 - 更推荐静态库:生成
libutils.a,然后// #cgo LDFLAGS: -L./lib -lutils - 别写
// #include "utils.c"——C 编译器会当真,然后报语法错
CGO 的“优雅”不在写法多漂亮,而在边界是否清晰:C 内存谁分配谁释放、头文件路径是否稳定、独立 C 文件是否被真正纳入构建流程。漏掉任意一环,问题都会在运行时或交叉编译时突然爆发,而且很难 debug。


















