
本文详解 go 与 c 互操作中“从 c 调用 go 函数”的标准实践,重点解决因重复声明、头文件包含不当导致的 `conflicting types for 'dummy'` 编译错误,并提供可直接运行的结构化示例。
在 Go 中通过 //export 指令将函数暴露给 C 使用,是实现 Go/C 混合编程的关键能力。但初学者常因 C 端声明方式不当而触发编译失败——典型错误如 conflicting types for 'dummy',其根源在于:Go 的 cgo 工具会自动生成 _cgo_export.c,其中已声明并定义了 dummy() 的 C 签名(int dummy(void)),若你在手动编写的 C 文件(如 wrapper.c)中再次用 extern int dummy(); 声明,就会与生成代码冲突。
✅ 正确做法是:C 端只声明需调用的 包装函数(如 testc),绝不声明 Go 导出函数本身;Go 导出函数的签名由 cgo 自动管理,C 代码只需按约定调用即可。
以下为推荐的工程化结构(三文件分离):
1. 头文件 dummy.h(仅声明 C 接口)
// dummy.h void testc();
2. C 实现文件 dummy.c(实现包装逻辑,不声明 dummy)
// dummy.c #include#include "dummy.h" // ✅ 关键:不声明 extern int dummy()! // cgo 会自动链接 Go 导出的 dummy 符号 void testc() { dummy(); // 直接调用,链接器自动解析 }
3. Go 主程序 main.go(导出 + 调用)
package main // #cgo CFLAGS: -Wno-error=implicit-function-declaration // #include// #include "dummy.h" // ✅ 只包含包装函数头文件 import "C" import "fmt" //export dummy func dummy() int { fmt.Println("hi you") return 0 } func main() { C.testc() // 调用 C 包装函数 }
? 关键注意事项:
- //export dummy 必须紧跟 import "C" 之前,且函数名首字母小写(Go 规则),但导出后 C 中以原名调用;
- dummy 的 C 签名固定为 int dummy(void)(无参数,返回 int),若需传参或返回其他类型,必须使用 *C.xxx 类型转换;
- 不要将 .c 文件直接 #include 进 Go 的 // #include 块中(如原例中的 "wrapper.c"),这会导致预处理阶段重复展开,加剧符号冲突;
- 编译时确保所有文件位于同一目录,执行 go run main.go 即可成功运行。
该结构清晰分离职责:Go 负责业务逻辑与导出,C 负责胶水层封装,既符合 cgo 设计哲学,也规避了绝大多数符号冲突问题。










