<p>import "C" 是 cgo 内联 C 代码的构建机制,非运行时调用;C 函数未定义因 cgo 仅解析紧贴其上的 / / 块、// #include 行及 // #cgo 指令,其他位置 C 代码全被忽略。</p>

Go 里用 import "C" 不是“调用 C”,而是让 cgo 把紧挨着它的 C 代码块编译进最终二进制——它本质是 Go 源码内联 C 的构建机制,不是运行时加载。
为什么 C.my_func 总报 undefined?
根本不是函数写错了,而是 cgo 根本没看到它。cgo 只解析三类东西:/* */ 注释块里的 C 代码、// #include 行指定的头文件、以及 // #cgo 指令。其他任何位置的 C 代码(比如普通 // 注释里、import 后面、或单独的 .c 文件)全被忽略。
-
/* */必须紧贴import "C"上方,中间不能有空行、不能有 Go 语句 -
// #include <stdio.h>这类指令必须写在import "C"前,且每行一个 - 如果函数定义在外部
.c文件里,仅#include "foo.h"不够——你还得确保该.c被编译器实际纳入链接,否则符号还是找不到
import "C" 前的 /* */ 里能写什么?
可以写完整 C 函数实现、结构体定义、宏、甚至内联汇编(只要目标平台支持),但所有内容必须符合 C 语法且能被系统 C 编译器(如 gcc/clang)直接编译。
- 函数必须有完整定义,不能只声明:
int add(int a, int b) { return a + b; }✅;int add(int, int);❌(除非在#include的头文件里) - 若需调用系统库函数(如
sqrt),除了#include <math.h>,还得加// #cgo LDFLAGS: -lm,否则链接失败 - 避免在
/* */里写 C++ 代码;若必须调用 C++ 库,头文件中要用extern "C"包裹声明
如何安全传字符串给 C 函数?
C.CString 分配的是 C 堆内存,Go 的 GC 完全不感知,漏掉 C.free 就是稳稳的内存泄漏;而 C.GoString 返回的是 Go 字符串副本,C 端原始内存仍需你负责释放。
立即学习“go语言免费学习笔记(深入)”;
- 传参:用
cstr := C.CString("hello"),之后必须配对defer C.free(unsafe.Pointer(cstr)) - 接收 C 返回的字符串时,先确认它是否指向堆内存(比如由
malloc或strdup分配);若返回的是栈变量地址(如char buf[64]; return buf;),直接C.GoString会读野指针 - 传递
[]byte给 C:用(*C.char)(unsafe.Pointer(&b[0]))获取指针,但长度必须额外传,C 侧无法从指针推断
静态链接 .a 文件的坑在哪?
cgo 默认按 -lfoo 查找动态库,即使同目录有 libfoo.a 也不会用——除非你绕过命名约定,直接把 .a 文件路径塞进 LDFLAGS。
- 正确写法:
// #cgo LDFLAGS: ./deps/libfoo.a(路径可为相对或绝对) - 错误写法:
// #cgo LDFLAGS: -L./deps -lfoo——这会让链接器优先找libfoo.so,找不到才试.a,且 macOS 上可能因 archive strip 行为导致符号丢失 - 交叉编译时,
libfoo.a必须是目标平台 ABI 编译的(例如为linux/arm64构建的库不能在darwin/amd64下链接)
最易被忽略的一点:cgo 不是魔法,它生成的 C 包装层和 Go 代码一起参与链接,所以所有 C 符号必须在编译期可见、类型匹配、ABI 兼容;一旦涉及跨平台或混合构建,${SRCDIR}、CC、CGO_ENABLED 这些环境变量的实际值,比代码本身更容易决定成败。


















