設定和使用 gccgo
本文件說明如何使用 gccgo,這是 Go 語言的編譯器。gccgo 編譯器是 GCC 的新前端,GCC 是廣泛使用的 GNU 編譯器。儘管前端本身採用 BSD 式授權,但 gccgo 通常作為 GCC 的一部分使用,並受 GNU 通用公共授權 (授權涵蓋 gccgo 本身作為 GCC 的一部分;不涵蓋 gccgo 所產生的程式碼) 的約束。
請注意,gccgo 並非 gc
編譯器;有關該編譯器的說明,請參閱 安裝 Go 說明。
版本
安裝 gccgo 最簡單的方式是安裝已建置為包含 Go 支援的 GCC 二進位版本。GCC 二進位版本可從 各種網站 取得,且通常包含在 GNU/Linux 發行版中。我們預期建置這些二進位版本的使用者大多會包含 Go 支援。
GCC 4.7.1 版本和所有後續的 4.7 版本都包含完整的 Go 1 編譯器和函式庫。
由於時間因素,GCC 4.8.0 和 4.8.1 版本接近但並非完全等同於 Go 1.1。GCC 4.8.2 版本包含完整的 Go 1.1.2 實作。
GCC 4.9 版本包含完整的 Go 1.2 實作。
GCC 5 版本包含完整的 Go 1.4 使用者函式庫實作。Go 1.4 執行時期並未完全合併,但 Go 程式不應可見此問題。
GCC 6 版本包含完整的 Go 1.6.1 使用者函式庫實作。Go 1.6 執行時期並未完全合併,但 Go 程式不應可見此問題。
GCC 7 版本包含完整的 Go 1.8.1 使用者函式庫實作。與較早版本一樣,Go 1.8 執行時期並未完全合併,但 Go 程式不應可見此問題。
GCC 8 版本包含完整的 Go 1.10.1 版本實作。Go 1.10 執行時期現已完全合併至 GCC 開發來源,且完全支援並行垃圾回收。
GCC 9 版本包含完整的 Go 1.12.2 版本實作。
GCC 10 版本包含完整的 Go 1.14.6 版本實作。
GCC 11 版本包含完整的 Go 1.16.3 版本實作。
GCC 12 和 13 版本包含完整的 Go 1.18 標準函式庫實作。然而,GCC 尚未包含對泛型的支援。
原始碼
如果您無法使用版本,或偏好自行建置 gccgo,則可透過 Git 存取 gccgo 原始碼。GCC 網站提供取得 GCC 原始碼的說明。其中包含 gccgo 原始碼。為方便起見,GCC 主要程式碼存放庫的 devel/gccgo
分支中提供 Go 支援的穩定版本:git://gcc.gnu.org/git/gcc.git
。此分支會定期更新為穩定的 Go 編譯器來源。
請注意,儘管 gcc.gnu.org
是取得 Go 前端原始碼最方便的方式,但並非主要來源所在位置。如果您想對 Go 前端編譯器進行變更,請參閱對 gccgo 進行貢獻。
建置
建置 gccgo 就如同建置 GCC,只需多加一或兩個選項。請參閱gcc 網站上的說明。執行 configure
時,請加入選項 --enable-languages=c,c++,go
(以及您可能想建置的其他語言)。如果您鎖定 32 位元 x86,則您會想建置 gccgo 以預設支援鎖定比較和交換指令;請同時使用 configure
選項 --with-arch=i586
(或更新的架構,視您的程式執行位置而定)來執行此動作。如果您鎖定 64 位元 x86,但有時想使用 -m32
選項,請使用 configure
選項 --with-arch-32=i586
。
Gold
在 x86 GNU/Linux 系統上,gccgo 編譯器能夠為 goroutine 使用小型非連續堆疊。這允許程式執行更多 goroutine,因為每個 goroutine 可以使用相對較小的堆疊。執行此動作需要使用 gold 連結器版本 2.22 或更新版本。您可以安裝 GNU binutils 2.22 或更新版本,或自行建置 gold。
要自行建置 gold,請建置 GNU binutils,並在執行 configure
程式碼時使用 --enable-gold=default
。在建置之前,您必須安裝 flex 和 bison 套件。典型的順序如下(您可以將 /opt/gold
替換為您有寫入權限的任何目錄):
git clone git://sourceware.org/git/binutils-gdb.git mkdir binutils-objdir cd binutils-objdir ../binutils-gdb/configure --enable-gold=default --prefix=/opt/gold make make install
無論您如何安裝 gold,當您設定 gccgo 時,請使用選項 --with-ld=GOLD_BINARY
。
先決條件
根據 gcc 網站 所述,建立 GCC 需要許多先決條件。在執行 gcc configure
指令碼之前,安裝所有先決條件非常重要。您可以使用 GCC 來源中的指令碼 contrib/download_prerequisites
方便地下載先決條件函式庫。
建立指令
安裝所有先決條件後,典型的建立和安裝順序如下所示(僅當您使用如上所述的 gold 連結器時才使用 --with-ld
選項)
git clone --branch devel/gccgo git://gcc.gnu.org/git/gcc.git gccgo mkdir objdir cd objdir ../gccgo/configure --prefix=/opt/gccgo --enable-languages=c,c++,go --with-ld=/opt/gold/bin/ld make make install
使用 gccgo
gccgo 編譯器與其他 gcc 前端類似。從 GCC 5 開始,gccgo 安裝也包含 go
指令碼的版本,可用於建立 Go 程式,如 https://go.dev.org.tw/cmd/go 所述。
不使用 go
指令碼編譯檔案
gccgo -c file.go
產生 file.o
。將檔案連結在一起以形成可執行檔
gccgo -o file file.o
若要執行產生的檔案,您需要告訴程式在哪裡可以找到已編譯的 Go 套件。有幾種方法可以做到這一點
-
設定
LD_LIBRARY_PATH
環境變數LD_LIBRARY_PATH=${prefix}/lib/gcc/MACHINE/VERSION [or] LD_LIBRARY_PATH=${prefix}/lib64/gcc/MACHINE/VERSION export LD_LIBRARY_PATH
這裡的
${prefix}
是建立 gccgo 時使用的--prefix
選項。對於二進位安裝,這通常是/usr
。是否使用lib
或lib64
取決於目標。通常lib64
適用於 x86_64 系統,而lib
適用於其他系統。其目的是命名找到libgo.so
的目錄。 -
連結時傳遞
-Wl,-R
選項(如果適用於您的系統,請將 lib 替換為 lib64)go build -gccgoflags -Wl,-R,${prefix}/lib/gcc/MACHINE/VERSION [or] gccgo -o file file.o -Wl,-R,${prefix}/lib/gcc/MACHINE/VERSION
-
使用
-static-libgo
選項以靜態連結已編譯的套件。 -
使用
-static
選項執行完全靜態連結(gc
編譯器的預設值)。
選項
gccgo 編譯器支援所有與語言無關的 GCC 選項,特別是 -O
和 -g
選項。
-fgo-pkgpath=PKGPATH
選項可用於設定要編譯的套件的唯一前置詞。此選項會由 go 指令自動使用,但如果您直接呼叫 gccgo,您可能會想要使用它。此選項旨在與包含許多套件的大型程式一起使用,以允許多個套件使用與套件名稱相同的識別碼。PKGPATH
可以是任何字串;一個好的字串選擇是用於匯入套件的路徑。
-I
和 -L
選項(編譯器的同義詞)可用於設定尋找匯入的搜尋路徑。如果您使用 go 指令建置,則不需要這些選項。
匯入
當您編譯匯出某項內容的檔案時,匯出資訊將直接儲存在物件檔案中。如果您直接使用 gccgo 建置,而不是使用 go 指令,則當您匯入套件時,您必須告訴 gccgo 如何尋找檔案。
當您使用 gccgo 匯入套件 FILE 時,它會在下列檔案中尋找匯入資料,並使用它找到的第一個檔案。
FILE.gox
libFILE.so
libFILE.a
FILE.o
FILE.gox
(如果使用)通常只會包含匯出資料。這可以透過下列方式從 FILE.o
產生:
objcopy -j .go_export FILE.o FILE.gox
gccgo 編譯器會在目前目錄中尋找匯入檔案。在更複雜的情況下,您可以將 -I
或 -L
選項傳遞給 gccgo。這兩個選項都會取得要搜尋的目錄。-L
選項也會傳遞給連結器。
gccgo 編譯器目前 (2015-06-15) 沒有在物件檔案中記錄已匯入套件的檔案名稱。您必須安排已匯入資料連結到程式中。同樣地,使用 go 指令建置時,不需要這麼做。
gccgo -c mypackage.go # Exports mypackage gccgo -c main.go # Imports mypackage gccgo -o main main.o mypackage.o # Explicitly links with mypackage.o
除錯
如果您在編譯時使用 -g
選項,您可以在可執行檔上執行 gdb
。除錯器僅具備有限的 Go 知識。您可以設定中斷點、單步執行等。您可以列印變數,但它們會以 C/C++ 類型列印。對於數字類型來說,這並不重要。Go 字串和介面會顯示為兩個元素的結構。Go 地圖和通道總是表示為執行時間結構的 C 指標。
C 互通性
使用 gccgo 時,與 C 或使用 extern "C"
編譯的 C++ 程式碼的互通性有限。
類型
基本類型直接對應:Go 中的 int32
在 C 中為 int32_t
,int64
為 int64_t
,依此類推。Go 類型 int
是與指標大小相同的整數,因此對應於 C 類型 intptr_t
。Go byte
等於 C unsigned char
。Go 中的指標在 C 中為指標。Go struct
與具有相同欄位和類型的 C struct
相同。
Go string
類型目前定義為兩個元素的結構(此定義 可能會變更)
struct __go_string { const unsigned char *__data; intptr_t __length; };
您無法在 C 和 Go 之間傳遞陣列。但是,Go 中陣列的指標等於 C 指標,指向等效元素類型。例如,Go *[10]int
等於 C int*
,假設 C 指標確實指向 10 個元素。
Go 中的切片是一個結構。目前的定義如下(此定義 可能會變更)
struct __go_slice { void *__values; intptr_t __count; intptr_t __capacity; };
Go 函式的類型是一個指向結構的指標(這可能會變更)。結構中的第一個欄位指向函式的程式碼,這將等於一個 C 函式的指標,其參數類型相當,並有一個額外的尾部參數。尾部參數是閉包,而要傳遞的引數是一個指向 Go 函式結構的指標。當一個 Go 函式傳回多個值時,C 函式會傳回一個結構。例如,這些函式大致相當
func GoFunction(int) (int, float64) struct { int i; float64 f; } CFunction(int, void*)
Go 的 interface
、channel
和 map
類型沒有對應的 C 類型(interface
是個二元結構,而 channel
和 map
是 C 中結構的指標,但這些結構是故意沒有文件說明的)。C 的 enum
類型對應到某個整數類型,但一般來說要準確預測是哪個類型是很困難的;請使用強制轉型。C 的 union
類型沒有對應的 Go 類型。包含位元欄位的 C struct
類型沒有對應的 Go 類型。C++ 的 class
類型沒有對應的 Go 類型。
C 和 Go 之間的記憶體配置完全不同,因為 Go 使用垃圾回收。這個領域中的確切準則尚未確定,但很可能會允許從 C 傳遞一個指向已配置記憶體的指標到 Go。最終釋放指標的責任將留在 C 端,當然,如果 C 端在 Go 端仍有副本時釋放指標,程式就會失敗。從 Go 傳遞指標到 C 時,Go 函式必須在某個 Go 變數中保留一個可見的副本。否則,Go 垃圾回收器可能會在 C 函式仍在使用指標時刪除該指標。
函式名稱
Go 程式碼可以使用 gccgo 中實作的 Go 擴充功能直接呼叫 C 函式:函式宣告前面可以加上 //extern NAME
。例如,以下是如何在 Go 中宣告 C 函式 open
//extern open func c_open(name *byte, mode int, perm int) int
C 函式自然會預期一個以 NUL 結尾的字串,在 Go 中這等於一個指向一個陣列(不是切片!)的指標,該陣列的元素是 byte
,並以一個零位元組結尾。因此,從 Go 呼叫的範例會像這樣(在匯入 syscall
套件之後)
var name = [4]byte{'f', 'o', 'o', 0}; i := c_open(&name[0], syscall.O_RDONLY, 0);
(這只是一個範例,要在 Go 中開啟檔案,請改用 Go 的 os.Open
函數)。
請注意,如果 C 函數會封鎖,例如呼叫 read
時,呼叫 C 函數可能會封鎖 Go 程式。除非你清楚了解自己在做什麼,否則所有 C 和 Go 之間的呼叫都應該透過 cgo 或 SWIG 實作,例如 gc
編譯器。
從 C 存取的 Go 函數名稱可能會變更。目前,沒有接收器的 Go 函數名稱為 prefix.package.Functionname
。前綴由編譯套件時使用的 -fgo-prefix
選項設定;如果沒有使用該選項,預設值為 go
。要從 C 呼叫函數,你必須使用 GCC 擴充功能設定名稱。
extern int go_function(int) __asm__ ("myprefix.mypackage.Function");
從 C 原始碼自動產生 Go 宣告
GCC 的 Go 版本支援從 C 程式碼自動產生 Go 宣告。此功能相當笨拙,大多數使用者應該改用 cgo 程式搭配 -gccgo
選項。
像往常一樣編譯你的 C 程式碼,並加入選項 -fdump-go-spec=FILENAME
。這會在編譯過程中產生檔案 FILENAME
。此檔案會包含 C 程式碼中宣告的類型、變數和函數的 Go 宣告。無法在 Go 中表示的 C 類型會記錄在 Go 程式碼中的註解中。產生的檔案不會有 package
宣告,但 gccgo 可以直接編譯它。
此程序充斥著未說明的警告和限制,我們無法保證它在未來不會變更。它比較適合當作實際 Go 程式碼的起點,而不是常規程序。