設定和使用 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 套件。有幾種方法可以做到這一點

選項

gccgo 編譯器支援所有與語言無關的 GCC 選項,特別是 -O-g 選項。

-fgo-pkgpath=PKGPATH 選項可用於設定要編譯的套件的唯一前置詞。此選項會由 go 指令自動使用,但如果您直接呼叫 gccgo,您可能會想要使用它。此選項旨在與包含許多套件的大型程式一起使用,以允許多個套件使用與套件名稱相同的識別碼。PKGPATH 可以是任何字串;一個好的字串選擇是用於匯入套件的路徑。

-I-L 選項(編譯器的同義詞)可用於設定尋找匯入的搜尋路徑。如果您使用 go 指令建置,則不需要這些選項。

匯入

當您編譯匯出某項內容的檔案時,匯出資訊將直接儲存在物件檔案中。如果您直接使用 gccgo 建置,而不是使用 go 指令,則當您匯入套件時,您必須告訴 gccgo 如何尋找檔案。

當您使用 gccgo 匯入套件 FILE 時,它會在下列檔案中尋找匯入資料,並使用它找到的第一個檔案。

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_tint64int64_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 的 interfacechannelmap 類型沒有對應的 C 類型(interface 是個二元結構,而 channelmap 是 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 程式碼的起點,而不是常規程序。