本文详解 cgo 中 `//export` 的正确用法,解决因头文件重复包含导致的 “undefined symbols” 和 “duplicate symbol” 链接错误,涵盖头文件分离、c 函数声明规范、导出函数签名要求及构建流程。
在 CGO 项目中,让 C 代码安全调用 Go 函数是常见需求,但极易因符号管理不当引发编译或链接失败。你遇到的两个核心错误——Undefined symbols for architecture x86_64 和后续的 duplicate symbol——本质源于同一根本问题:C 实现文件(test.c)被直接 #include 进 Go 源码的 C 块中,导致其定义被 cgo 构建系统重复解析和链接。
cgo 在构建时会自动生成 _cgo_export.o 目标文件,其

✅ 正确做法是严格遵循 C 语言模块化原则:头文件(.h)仅声明,源文件(.c)只实现,Go 侧仅通过头文件声明调用 C 函数,不参与 C 实现的编译。
1. 创建头文件 test.h(声明接口)
#ifndef TEST_H_ #define TEST_H_ #include// 声明 C 函数,供 Go 调用 char* myprint(char *msg); #endif
2. 创建实现文件 test.c(定义逻辑,不包含 Go 导出函数)
#include#include "test.h" // 声明外部 Go 导出函数(必须与 Go 中 //export 签名完全一致) extern void receiveC(char *msg); char* myprint(char *msg) { receiveC(msg); // 安全调用 Go 函数 return msg; }
3. Go 文件 example.go(仅引用头文件,正确导出)
package main
/*
#cgo CFLAGS: -I.
#include "test.h"
*/
import "C"
import "fmt"
//export receiveC
func receiveC(msg *C.char) {
fmt.Println(C.GoString(msg))
}
func Example() {
fmt.Println("this is go")
result := C.GoString(C.myprint(C.CString("go!!")))
fmt.Println("C returned:", result)
}
func main() {
Example()
}⚠️ 关键注意事项:
go build -o cgo_example . ./cgo_example
输出应为:
this is go go!! C returned: go!!
? 总结:CGO 不是“把 C 代码粘贴进 Go”,而是构建跨语言 ABI 边界。坚持“头文件声明 + 源文件实现 + Go 仅声明调用”的三层隔离,是规避符号冲突、确保链接成功的黄金准则。