CGO 是 Go 和 C 世界之间的桥梁,它让你可以用 Go 写业务逻辑、用 C 写高性能或底层部分。但它同时也引入了边界成本、内存模型差异和构建复杂度,是一把真正的"双刃剑"。
一、CGO 是什么?底层原理是什么?
CGO的本质:从构建角度理解CGO才是理解它的关键。
1.1 Go在构建阶段扫描 import "C"的文件
1.2 解析顶端/*...*/内的C代码和声明
1.3 自动生成中间文件:C的桥接文件和Go wrapper
1.4 编译器将你的Go代码+自动生成的C文件,一起交给C 工具链(gcc/clang)构建
1.5 最终链接成一个二进制文件
二、基本使用方式
注意:如果在windows下,请先确保gcc的环境安装好了,如果没有,请去jmeubank.github.io/tdm-gcc/下载安...
2.1 调用C函数
bash
//clang.go
package main
/*
int add(int a, int b) {
return a+b;
}
*/
import "C"
func AddFromClang(a, b int) int {
return int(C.add(C.int(a), C.int(b)))
}
//main.go
package main
import "fmt"
func main() {
fmt.Println("Hello, Codee君!")
r := AddFromClang(3, 3)
fmt.Println(r)
}
// $ set CGO_ENABLED=1 && go run .
// Hello, Codee君!
// 6
注意两点:
-
import "C"必须紧跟/* C 代码 */ -
C 函数命名空间在
C.下面
2.2 调用外部C头文件/库
bash
/*
#include <math.h>
*/
import "C"
func PowFromClang(a, b float64) float64 {
return float64(C.pow(C.double(a), C.double(b)))
}
p := PowFromClang(2, 3)
fmt.Println(p)
// output
// 8
2.3 Go调用C,C再调用Go
Go导出函数给C用
bash
// golang/main.go
package main
import "C"
import "fmt"
//go的main函数不会在c调用里执行
func main() {
fmt.Println("Hello, Codee君!")
}
//export GoPrint
func GoPrint(i C.int) {
fmt.Println("GoPrint:", int(i))
}
配合C调用
bash
// clang/main.c
#include <stdio.h>
extern void GoPrint(int);
int main() {
printf("C start\n");
GoPrint(666);
GoPrint(888);
printf("C end\n");
return 0;
}
编译命令如下:
bash
$ cd golang
$ set CGO_ENABLED=1 && go build -buildmode=c-archive -o ../clang/libcodeejun.a
$ cd ../clang/
$ gcc -o main.exe main.c libcodeejun.a
$ chmod +x main.exe
$ ./main.exe
C start
GoPrint: 666
GoPrint: 888
C end
注意:使用 //export 的 Go 文件不能使用 cgo 的 package main 之外的 build tag,否则会失败。
三、C 与Go之间的内存与类型转换
3.1 Go-> C 类型转换规则
|----------|--------|
| Go类型 | C类型 |
| C.int | int |
| C.char | char |
| C.double | double |
但是:
Go string -> C string 必须手动分配
bash
cs := C.CString("hello")
defer C.free(unsafe.Pointer(cs))
C返回的内存Go不知道怎么释放。必须保证:
-
C分配的内存由C释放
-
Go分配的内存由Go释放
-
禁止跨语言释放(free)
四、CGO性能陷阱
4.1 CGO调用一次的成本:数百纳秒到几微秒。原因:
-
线程模型切换
-
调度器同步
-
stack切换
-
panic -> errno 处理
-
GC边界同步
因此:CGO不是FFI(Foreign Function Interface),它比Rust的FFI、C++的extern C 成本都要高很多。
4.2 CGO调用成本至高原则
少调、多算。
bash
//❌ 禁止每次循环都调用C
for i := 0; i < N; i++ {
C.add(1,2)
}
bash
//✔️ 把循环放在C里
int sumN(int n) {
int s = 0;
for (int i = 0; i < n; i++) s += add(1,2);
return s;
}
4.3 指针越界与逃逸问题
Go的GC不能扫描C内存,Go指针不能随便传给C:
-
不能把Go指针指向的对象交给C保存
-
不能把Go指针跨C调用存活太久
五、什么时候应该用CGO?什么时候不应该用?
5.1 适合用CGO:
-
调用已有C底层库(OpenSSL、libxml、ffmpeg)
-
重用成熟的C算法(压缩、加密、图像处理)
-
Go无法直接实现的系统API
5.2 不适合用CGO:
-
小函数、频繁调用
-
只为了"省事"
-
想写高性能纯计算
如果为例性能而写C,那么别用CGO,直接用Go+unsafe更快。
CGO就像是一座收费桥,你能跨过去,但每次都要付"高昂的过桥费"(开销大),而且桥两头的城市(Go/C内存模型)完全不同。所以,能不用CGO就不用。
*源码地址*
1、公众号"Codee君"回复"每日一Go"获取源码
2、pan.baidu.com/s/1B6pgLWfS...
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!