Go 語言 編譯和鏈接參數(shù)

2023-03-22 15:01 更新

原文鏈接:https://chai2010.cn/advanced-go-programming-book/ch2-cgo/ch2-10-link.html


2.10 編譯和鏈接參數(shù)

編譯和鏈接參數(shù)是每一個 C/C++ 程序員需要經(jīng)常面對的問題。構(gòu)建每一個 C/C++ 應(yīng)用均需要經(jīng)過編譯和鏈接兩個步驟,CGO 也是如此。 本節(jié)我們將簡要討論 CGO 中經(jīng)常用到的編譯和鏈接參數(shù)的用法。

2.10.1 編譯參數(shù):CFLAGS/CPPFLAGS/CXXFLAGS

編譯參數(shù)主要是頭文件的檢索路徑,預(yù)定義的宏等參數(shù)。理論上來說 C 和 C++ 是完全獨(dú)立的兩個編程語言,它們可以有著自己獨(dú)立的編譯參數(shù)。 但是因為 C++ 語言對 C 語言做了深度兼容,甚至可以將 C++ 理解為 C 語言的超集,因此 C 和 C++ 語言之間又會共享很多編譯參數(shù)。 因此 CGO 提供了 CFLAGS/CPPFLAGS/CXXFLAGS 三種參數(shù),其中 CFLAGS 對應(yīng) C 語言編譯參數(shù) (以 .c 后綴名)、 CPPFLAGS 對應(yīng) C/C++ 代碼編譯參數(shù) (.c,.cc,.cpp,.cxx)、CXXFLAGS 對應(yīng)純 C++ 編譯參數(shù) (.cc,.cpp,*.cxx)。

2.10.2 鏈接參數(shù):LDFLAGS

鏈接參數(shù)主要包含要鏈接庫的檢索目錄和要鏈接庫的名字。因為歷史遺留問題,鏈接庫不支持相對路徑,我們必須為鏈接庫指定絕對路徑。 cgo 中的 ${SRCDIR} 為當(dāng)前目錄的絕對路徑。經(jīng)過編譯后的 C 和 C++ 目標(biāo)文件格式是一樣的,因此 LDFLAGS 對應(yīng) C/C++ 共同的鏈接參數(shù)。

2.10.3 pkg-config

為不同 C/C++ 庫提供編譯和鏈接參數(shù)是一項非常繁瑣的工作,因此 cgo 提供了對應(yīng) pkg-config 工具的支持。 我們可以通過 #cgo pkg-config xxx 命令來生成 xxx 庫需要的編譯和鏈接參數(shù),其底層通過調(diào)用 pkg-config xxx --cflags 生成編譯參數(shù),通過 pkg-config xxx --libs 命令生成鏈接參數(shù)。 需要注意的是 pkg-config 工具生成的編譯和鏈接參數(shù)是 C/C++ 公用的,無法做更細(xì)的區(qū)分。

pkg-config 工具雖然方便,但是有很多非標(biāo)準(zhǔn)的 C/C++ 庫并沒有實現(xiàn)對其支持。 這時候我們可以手工為 pkg-config 工具創(chuàng)建對應(yīng)庫的編譯和鏈接參數(shù)實現(xiàn)支持。

比如有一個名為 xxx 的 C/C++ 庫,我們可以手工創(chuàng)建 /usr/local/lib/pkgconfig/xxx.pc 文件:

Name: xxx
Cflags:-I/usr/local/include
Libs:-L/usr/local/lib –lxxx2

其中 Name 是庫的名字,Cflags 和 Libs 行分別對應(yīng) xxx 使用庫需要的編譯和鏈接參數(shù)。如果 pc 文件在其它目錄, 可以通過 PKG_CONFIG_PATH 環(huán)境變量指定 pkg-config 工具的檢索目錄。

而對應(yīng) cgo 來說,我們甚至可以通過 PKG_CONFIG 環(huán)境變量可指定自定義的 pkg-config 程序。 如果是自己實現(xiàn) CGO 專用的 pkg-config 程序,只要處理 --cflags 和 --libs 兩個參數(shù)即可。

下面的程序是 macos 系統(tǒng)下生成 Python3 的編譯和鏈接參數(shù):

// py3-config.go
func main() {
    for _, s := range os.Args {
        if s == "--cflags" {
            out, _ := exec.Command("python3-config", "--cflags").CombinedOutput()
            out = bytes.Replace(out, []byte("-arch"), []byte{}, -1)
            out = bytes.Replace(out, []byte("i386"), []byte{}, -1)
            out = bytes.Replace(out, []byte("x86_64"), []byte{}, -1)
            fmt.Print(string(out))
            return
        }
        if s == "--libs" {
            out, _ := exec.Command("python3-config", "--ldflags").CombinedOutput()
            fmt.Print(string(out))
            return
        }
    }
}

然后通過以下命令構(gòu)建并使用自定義的 pkg-config 工具:

$ go build -o py3-config py3-config.go
$ PKG_CONFIG=./py3-config go build -buildmode=c-shared -o gopkg.so main.go

具體的細(xì)節(jié)可以參考 Go 實現(xiàn) Python 模塊章節(jié)。

2.10.4 go get 鏈

在使用 go get 獲取 Go 語言包的同時會獲取包依賴的包。比如 A 包依賴 B 包,B 包依賴 C 包,C 包依賴 D 包: pkgA -> pkgB -> pkgC -> pkgD -> ...。再 go get 獲取 A 包之后會依次線獲取 BCD 包。 如果在獲取 B 包之后構(gòu)建失敗,那么將導(dǎo)致鏈條的斷裂,從而導(dǎo)致 A 包的構(gòu)建失敗。

鏈條斷裂的原因有很多,其中常見的原因有:

  • 不支持某些系統(tǒng), 編譯失敗
  • 依賴 cgo, 用戶沒有安裝 gcc
  • 依賴 cgo, 但是依賴的庫沒有安裝
  • 依賴 pkg-config, windows 上沒有安裝
  • 依賴 pkg-config, 沒有找到對應(yīng)的 bc 文件
  • 依賴 自定義的 pkg-config, 需要額外的配置
  • 依賴 swig, 用戶沒有安裝 swig, 或版本不對

仔細(xì)分析可以發(fā)現(xiàn),失敗的原因中和 CGO 相關(guān)的問題占了絕大多數(shù)。這并不是偶然現(xiàn)象, 自動化構(gòu)建 C/C++ 代碼一直是一個世界難題,到目前位置也沒有出現(xiàn)一個大家認(rèn)可的統(tǒng)一的 C/C++ 管理工具。

因為用了 cgo,比如 gcc 等構(gòu)建工具是必須安裝的,同時盡量要做到對主流系統(tǒng)的支持。 如果依賴的 C/C++ 包比較小并且有源代碼的前提下,可以優(yōu)先選擇從代碼構(gòu)建。

比如 github.com/chai2010/webp 包通過為每個 C/C++ 源文件在當(dāng)前包建立關(guān)鍵文件實現(xiàn)零配置依賴:

// z_libwebp_src_dec_alpha.c
#include "./internal/libwebp/src/dec/alpha.c"

因此在編譯 z_libwebp_src_dec_alpha.c 文件時,會編譯 libweb 原生的代碼。 其中的依賴是相對目錄,對于不同的平臺支持可以保持最大的一致性。

2.10.5 多個非 main 包中導(dǎo)出 C 函數(shù)

官方文檔說明導(dǎo)出的 Go 函數(shù)要放 main 包,但是真實情況是其它包的 Go 導(dǎo)出函數(shù)也是有效的。 因為導(dǎo)出后的 Go 函數(shù)就可以當(dāng)作 C 函數(shù)使用,所以必須有效。但是不同包導(dǎo)出的 Go 函數(shù)將在同一個全局的名字空間,因此需要小心避免重名的問題。 如果是從不同的包導(dǎo)出 Go 函數(shù)到 C 語言空間,那么 cgo 自動生成的 _cgo_export.h 文件將無法包含全部導(dǎo)出的函數(shù)聲明, 我們必須通過手寫頭文件的方式聲明導(dǎo)出的全部函數(shù)。



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號