設定和使用 gccgo
本文件介紹瞭如何使用 gccgo,這是一個 Go 語言的編譯器。gccgo 編譯器是 GCC(一款廣泛使用的 GNU 編譯器)的一個新前端。儘管前端本身遵循 BSD 風格的許可,但 gccgo 通常作為 GCC 的一部分使用,因此受 GNU 通用公共許可證 的約束(該許可證涵蓋了作為 GCC 一部分的 gccgo 本身;它不涵蓋由 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 原始碼已包含在內。為了方便起見,Go 支援的穩定版本可以在主 GCC 程式碼倉庫的 devel/gccgo
分支中獲取: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
命令版本,可以用於按照 https://golang.org.tw/cmd/go 中的說明構建 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 中的 slice 是一個結構。當前定義是(此定義可能會更改)
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 程式碼可以直接呼叫 C 函式,使用 gccgo 中實現的一個 Go 擴充套件:函式宣告可以前置 //extern NAME
。例如,C 函式 open
在 Go 中宣告如下
//extern open func c_open(name *byte, mode int, perm int) int
C 函式自然期望一個以 NUL 結尾的字串,在 Go 中這等同於一個指向 byte
陣列(不是 slice!)的指標,並且有一個終止的零位元組。所以從 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 宣告。該功能相當笨拙,大多數使用者應該改用帶有 -gccgo
選項的 cgo 程式。
像往常一樣編譯您的 C 程式碼,並新增選項 -fdump-go-spec=FILENAME
。這將作為編譯的副作用建立檔案 FILENAME
。該檔案將包含 C 程式碼中宣告的型別、變數和函式的 Go 宣告。無法在 Go 中表示的 C 型別將被記錄為 Go 程式碼中的註釋。生成的檔案將沒有 package
宣告,但除此之外可以由 gccgo 直接編譯。
此過程充滿了未說明的特殊情況和限制,我們不保證它將來不會發生變化。它比常規過程更有可能成為實際 Go 程式碼的起點。