CGO:Go 与 C/C++ 的无缝集成

CGO 是什么

CGO 让 Go 程序直接调用 C/C++ 代码——复用成熟 C 库、对接底层系统能力、性能敏感路径用 C 实现。代价是引入 cgo 调用开销、跨语言内存管理复杂度、编译依赖(需要 C 编译器)。

基础语法

/*
// C 或 C++ 代码
#include <stdio.h>
*/
import "C"      // 必须紧跟注释块,中间不能有空行

func main() {
    C.puts(C.CString("Hello from CGO!"))
}

三个要点:注释块里的内容交给 cgo 预处理;import "C" 必须紧跟注释块;通过 C. 前缀访问 C 函数和类型。

类型映射

Go 类型C 类型
C.char / C.int / C.floatchar / int / float
*C.charchar*
unsafe.Pointervoid*

调用 C++:extern “C” 包装

C++ 的函数名会被修饰、还有异常机制,不能直接跨 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"

内存管理原则

跨语言内存的铁律:谁分配谁释放。Go 侧创建的 C 字符串必须显式 free:

/*
#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++ 侧

// 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> <queue> <climits> <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 切片
    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)
}

跨平台编译

# 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

性能优化

  • 减少跨语言调用次数:批量传数据,别逐元素调 C(cgo 调用开销几十 ns/次,批量能摊薄);
  • static inline C 函数减少调用开销;
  • 大块内存分配在 C 侧用内存池复用。

常见问题

报错原因与解决
cannot convert *int to *_Ctype_int类型不匹配:Go 切片用 int32/int64 显式对齐 C 的 int(平台相关)
fatal error: vector: No such file or directoryC++ 头文件找不到:加 #cgo CXXFLAGS: -std=c++11 并确认编译器是 g++
undefined reference to ‘dijkstra’链接问题:确认函数是 extern “C”、库路径正确(-L. -ldijkstra)、符号签名一致

安全注意

  • 指针安全:C 侧不能长期持有 Go 指针(Go 的 GC 会移动对象);跨 cgo 边界传递的 Go 指针要遵守 cgo 指针规则;
  • 边界检查:C 数组访问不做越界检查,Go 侧用 unsafe.Slice 时长度必须可信;
  • 错误处理:C 函数返回 -1/errno 时映射为 Go 的 error。
滚动至顶部