cgo 完整指南:Go 與 C/C++ 互通實務
cgo 是 Go 提供的跨語言橋接機制,讓 Go 程式可以直接呼叫 C 程式碼,也能讓 C 程式反向呼叫 Go。當你需要整合現成的 C/C++ 函式庫、使用底層系統 API,或處理極端效能熱區時,cgo 會是實用選項。
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" 時,會進行幾個步驟:
- 解析
import "C"上方的 C 程式碼或#include宣告。 - 產生 Go 與 C 之間的橋接程式碼。
- 呼叫系統上的 C 編譯器,例如 GCC 或 Clang。
- 將 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 之間的字串、指標與記憶體生命週期要特別小心。
常見陷阱與最佳實踐
常見陷阱
- 呼叫次數過多:Go 與 C 之間切換有成本,頻繁小呼叫很容易拖慢整體效能。
- 記憶體洩漏:C 端用
malloc配置的記憶體,不會被 Go GC 自動回收。 - 指標誤用:把 Go 指標交給 C 長期持有,可能導致未定行為或執行期錯誤。
- 部署變複雜:需要額外準備 C 編譯器、標頭檔、共享函式庫與連結設定。
- 跨平台變困難:一旦用到 cgo,交叉編譯就不再是單純設定
GOOS、GOARCH。
最佳實踐
- 降低跨語言邊界次數:一次傳大批資料,比多次小呼叫更合理。
- 封裝 cgo 程式碼:把 cgo 集中在少數 package,避免汙染整個程式架構。
- 明確記憶體責任:誰配置、誰釋放,必須寫清楚。
- 先找純 Go 替代方案:能不用 cgo,就少一層維護成本。
- 加強整合測試: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 通常會更穩定、可攜且容易維護。