设置和使用 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.ac.cn/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 代码的起点。