Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

cgo 完整指南:Go 與 C/C++ 互通實務

cgo 是 Go 提供的跨語言橋接機制,讓 Go 程式可以直接呼叫 C 程式碼,也能讓 C 程式反向呼叫 Go。當你需要整合現成的 C/C++ 函式庫、使用底層系統 API,或處理極端效能熱區時,cgo 會是實用選項。

Go 標誌

C 語言標誌

cgo 很強,但不是預設選項。能用純 Go 解決時,通常仍優先選純 Go。

目錄

什麼是 cgo

cgo 是 Go 工具鏈的一部分,透過特殊的 import "C" 語法,讓 Go 程式能夠:

  • 呼叫 C 函式
  • 使用 C 型別、常數與結構
  • 連結外部 C 或 C++ 函式庫
  • 將 Go 函式匯出給 C 呼叫

它不是獨立語言,也不是額外框架,而是 Go 在編譯階段提供的跨語言互通能力。

為什麼需要 cgo

以下是最常見的使用原因:

  • 重用既有函式庫:例如 OpenCV、FFmpeg、SQLite、各式驅動程式或數值運算函式庫。
  • 接觸底層系統能力:某些作業系統 API 或硬體介面只有 C 層封裝最完整。
  • 整合遺留系統:企業常有大量穩定運行的 C/C++ 模組,不會因為導入 Go 就全部重寫。
  • 處理效能熱區:少數極度仰賴 SIMD、手寫最佳化或成熟原生函式庫的場景,C/C++ 仍有優勢。

但 cgo 同時會帶來記憶體管理、編譯流程與部署複雜度,因此不應為了「好像比較快」就直接採用。

cgo 的運作原理

當 Go 編譯器看到 import "C" 時,會進行幾個步驟:

  1. 解析 import "C" 上方的 C 程式碼或 #include 宣告。
  2. 產生 Go 與 C 之間的橋接程式碼。
  3. 呼叫系統上的 C 編譯器,例如 GCC 或 Clang。
  4. 將 Go 目標檔、C 目標檔與橋接程式碼一起連結成最終產物。

這也是為什麼只要用了 cgo,就必須依賴本機 C 編譯器與對應函式庫。

實戰:Go 呼叫 C 函式

下面是一個最小可執行範例,示範 Go 直接呼叫 C 的 add 函式。

package main

/*
#include <stdio.h>

int add(int a, int b) {
    printf("C 函式正在執行加法\n");
    return a + b;
}
*/
import "C"

import "fmt"

func main() {
    result := C.add(C.int(10), C.int(20))
    fmt.Printf("計算結果:%d\n", int(result))
}

重點

  • import "C" 前方的註解區塊會被當成 C 程式碼處理。
  • Go 呼叫 C 時,通常要明確做型別轉換,例如 C.int(10)
  • 回傳值若要在 Go 端使用,通常也會再轉回 Go 型別。

執行方式

go run add.go

實戰:C 呼叫 Go 函式

若要讓 C 呼叫 Go,需要使用 //export 將 Go 函式匯出。

Go 程式

package main

/*
#include <stdio.h>
*/
import "C"

import "fmt"

//export MyGoFunction
func MyGoFunction(name *C.char, age C.int) {
    fmt.Printf("哈囉 %s,你今年 %d 歲。\n", C.GoString(name), int(age))
}

func main() {}

C 程式

#include "_cgo_export.h"
#include <stdio.h>

int main(void) {
    MyGoFunction("小明", 30);
    return 0;
}

編譯方式

先將 Go 編譯成可供 C 連結的靜態函式庫:

go build -buildmode=c-archive -o mygoapp.a go_callback.go

再使用 C 編譯器連結:

gcc -o my_c_program main.c mygoapp.a -lpthread

注意事項

  • _cgo_export.h 由 Go 工具鏈自動產生。
  • //export 只能放在符合 cgo 規則的位置。
  • C 與 Go 之間的字串、指標與記憶體生命週期要特別小心。

常見陷阱與最佳實踐

常見陷阱

  1. 呼叫次數過多:Go 與 C 之間切換有成本,頻繁小呼叫很容易拖慢整體效能。
  2. 記憶體洩漏:C 端用 malloc 配置的記憶體,不會被 Go GC 自動回收。
  3. 指標誤用:把 Go 指標交給 C 長期持有,可能導致未定行為或執行期錯誤。
  4. 部署變複雜:需要額外準備 C 編譯器、標頭檔、共享函式庫與連結設定。
  5. 跨平台變困難:一旦用到 cgo,交叉編譯就不再是單純設定 GOOSGOARCH

最佳實踐

  1. 降低跨語言邊界次數:一次傳大批資料,比多次小呼叫更合理。
  2. 封裝 cgo 程式碼:把 cgo 集中在少數 package,避免汙染整個程式架構。
  3. 明確記憶體責任:誰配置、誰釋放,必須寫清楚。
  4. 先找純 Go 替代方案:能不用 cgo,就少一層維護成本。
  5. 加強整合測試:cgo 問題常出在執行期,不是編譯期。

適合使用 cgo 的場景

以下情況通常值得考慮 cgo:

  • 需要整合成熟的 C/C++ 函式庫
  • 需要直接操作系統底層 API 或硬體
  • 需要逐步接手舊有 C/C++ 系統
  • 已確認某段邏輯是效能瓶頸,且原生函式庫已存在成熟實作

以下情況通常不建議:

  • 只是想「可能比較快」
  • 功能可以用純 Go 函式庫取代
  • 專案非常重視簡單部署與跨平台發佈

FAQ

cgo 會讓程式變慢嗎?

有可能。單次呼叫成本通常比純 Go 函式高,因此不適合做大量細碎呼叫。若每次呼叫都能處理大量工作,成本才比較值得。

cgo 會影響 Goroutine 嗎?

會。Go 呼叫 C 時,執行中的 Goroutine 可能會佔用作業系統執行緒較久,影響排程效率。長時間阻塞的 C 呼叫尤其要小心。

cgo 可以跨平台編譯嗎?

可以,但通常需要對應平台的 C 交叉編譯工具鏈,難度遠高於純 Go。

使用 cgo 安全嗎?

安全性取決於你整合的 C/C++ 程式碼品質。記憶體越界、use-after-free、緩衝區溢位等問題,都可能直接影響 Go 程式。

結論

cgo 讓 Go 不必孤軍奮戰,能夠直接借力成熟的 C/C++ 生態系。不過它帶來的不是免費能力,而是額外的工程成本。正確的策略通常不是「先用 cgo」,而是「確定真的需要,再精準地用 cgo」。

如果你的目標是整合既有原生函式庫、補足系統能力或處理已確認的效能熱區,cgo 很有價值;如果只是一般應用開發,純 Go 通常會更穩定、可攜且容易維護。