CGO 是什么
CGO 让 Go 程序直接调用 C/C++ 代码。典型动机:复用成熟 C 库、对接底层系统能力、性能敏感路径用 C 实现。代价也要说清:cgo 调用有固定开销,跨语言内存管理变复杂,构建环境必须带 C 编译器。
基础语法
CGO 的入口是 import "C"——它必须紧跟一段注释块,注释里写 C 代码、cgo 指令和头文件。注释块与 import 之间不能有空行。
/*
// C 或 C++ 代码
#include <stdio.h>
*/
import "C" // 必须紧跟注释块,中间不能有空行
func main() {
C.puts(C.CString("Hello from CGO!"))
}
三个要点:注释块里的内容交给 cgo 预处理;import "C" 必须紧跟注释块;C 的函数、类型、常量都通过 C. 前缀访问。
类型映射
跨边界的类型要显式对应。C 的 int 宽度平台相关(4 或 8 字节),Go 侧用 int32/int64 显式对齐,别用 Go 的 int 想当然。

| Go 类型 | C 类型 |
|---|---|
| C.char / C.int / C.float | char / int / float |
| *C.char | char* |
| unsafe.Pointer | void* |
| []int32 + unsafe.Pointer | int*(数组首元素指针) |
调用 C++:extern “C” 包装
C++ 的函数名会被修饰(name mangling)、还有异常机制,不能直接跨 cgo 边界。标准做法是加一层 extern "C" 包装:
// mylib.h
#ifdef __cplusplus
extern "C" {
#endif
void cppFunction();
#ifdef __cplusplus
}
#endif
/*
#cgo CXXFLAGS: -std=c++11
#cgo LDFLAGS: -L. -lmylib
#include "mylib.h"
*/
import "C"
CXXFLAGS 声明 C++ 编译选项,LDFLAGS 声明链接库。更复杂的 C++ 类集成(对象生命周期、异常转换)见《如何将C++代码包装成C接口》。
内存管理原则
跨语言内存的铁律:谁分配谁释放。Go 侧创建的 C 字符串必须显式 free,C 侧分配的结构必须调用 C 侧的释放函数——两边各自维护自己的堆。
/*
#include <stdlib.h>
*/
import "C"
import "unsafe"
func main() {
cstr := C.CString("Hello")
defer C.free(unsafe.Pointer(cstr)) // 必须释放,否则泄漏
C.puts(cstr)
}
实战:Dijkstra 集成(C++ 实现 + Go 调用)
一个完整链路:C++ 实现算法,用 extern "C" 导出 C 接口,返回堆分配的结果结构体,Go 侧调用并负责释放。
C++ 侧
// dijkstra.h
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
int* path;
int length;
int total_cost;
} PathResult;
PathResult* dijkstra(int graph[], int vertices, int start, int end);
void free_path_result(PathResult* result);
#ifdef __cplusplus
}
#endif
// dijkstra.cpp(核心逻辑)
#include "dijkstra.h"
#include <vector>
#include <queue>
#include <climits>
#include <algorithm>
using namespace std;
PathResult* dijkstra(int graph[], int vertices, int start, int end) {
vector<int> dist(vertices, INT_MAX), prev(vertices, -1);
vector<bool> visited(vertices, false);
auto w = [&](int u, int v) { return graph[u * vertices + v]; };
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
pq.push({0, start});
dist[start] = 0;
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (visited[u]) continue;
visited[u] = true;
for (int v = 0; v < vertices; ++v) {
int weight = w(u, v);
if (weight > 0 && d + weight < dist[v]) {
dist[v] = d + weight;
prev[v] = u;
pq.push({dist[v], v});
}
}
}
// 回溯路径 → C 数组,封装进 PathResult
vector<int> path;
for (int at = end; at != -1; at = prev[at])
path.push_back(at);
reverse(path.begin(), path.end());
PathResult* r = new PathResult();
r->length = path.size();
r->path = new int[path.size()];
copy(path.begin(), path.end(), r->path);
r->total_cost = dist[end];
return r;
}
void free_path_result(PathResult* r) {
if (r) { delete[] r->path; delete r; }
}
Go 侧
package main
/*
#cgo CXXFLAGS: -std=c++11
#cgo LDFLAGS: -L. -ldijkstra
#include "dijkstra.h"
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
// 邻接矩阵,用 int32 保证与 C.int 大小一致
graph := []int32{
0, 10, 0, 30, 100,
0, 0, 50, 0, 0,
0, 0, 0, 0, 10,
0, 0, 20, 0, 60,
0, 0, 0, 0, 0,
}
result := C.dijkstra(
(*C.int)(unsafe.Pointer(&graph[0])), // 切片 → C 数组指针
C.int(5), C.int(0), C.int(4),
)
defer C.free_path_result(result) // C 侧分配,C 侧释放
// unsafe 把 C 数组转回 Go 切片(unsafe.Slice 需 Go 1.17+)
path := unsafe.Slice((*C.int)(unsafe.Pointer(result.path)), result.length)
goPath := make([]int, len(path))
for i := range path {
goPath[i] = int(path[i])
}
fmt.Println("Path:", goPath)
fmt.Println("Total cost:", result.total_cost)
}

图 2:编译运行输出——g++ 编译 C++ 库、go build、运行打印最短路径 Path: [0 3 2 4]
跨平台编译


# Linux/macOS
g++ -std=c++11 -fPIC -shared dijkstra.cpp -o libdijkstra.so
CGO_CXXFLAGS="-std=c++11" go build
# Windows
g++ -std=c++11 -shared dijkstra.cpp -o dijkstra.dll -Wl,--out-implib,libdijkstra.a
set CGO_CXXFLAGS=-std=c++11
go build
性能优化
- 减少跨语言调用次数:cgo 调用开销几十 ns/次,批量传数据、别逐元素调 C,把调用摊薄;
- 热路径用
static inline的 C 函数,省掉一层调用开销; - 大块内存分配在 C 侧用内存池复用,避免频繁跨边界分配。
常见问题
| 报错 | 原因与解决 |
|---|---|
| cannot convert *int to *_Ctype_int | 类型不匹配:Go 切片用 int32/int64 显式对齐 C 的 int(平台相关) |
| fatal error: vector: No such file or directory | C++ 头文件找不到:加 #cgo CXXFLAGS: -std=c++11,确认编译器是 g++ 而非 gcc |
| undefined reference to ‘dijkstra’ | 链接问题:确认函数是 extern "C"、库路径正确(-L. -ldijkstra)、符号签名一致 |
安全注意
- 指针规则:C 侧不能长期持有 Go 指针(Go 的 GC 会移动对象);跨 cgo 边界传递的 Go 指针要遵守 cgo 指针规则(Go 指针可以传给 C,但 C 不能把 Go 指针存起来);
- 边界检查:C 数组访问不做越界检查,Go 侧用 unsafe.Slice 时 length 必须可信;
- 错误处理:C 函数返回 -1/errno 时,Go 侧要映射为 error,别吞掉。

