设置和使用 gccgo
本文档解释了如何使用 gccgo,一种 Go 语言编译器。gccgo 编译器是 GCC(广泛使用的 GNU 编译器)的一个新前端。尽管前端本身采用 BSD 样式许可证,但 gccgo 通常作为 GCC 的一部分使用,并受 GNU 通用公共许可证 约束(该许可证将 gccgo 本身作为 GCC 的一部分进行约束;它不约束由 gccgo 生成的代码)。
请注意,gccgo 不是 gc
编译器;有关该编译器的说明,请参阅 安装 Go 说明。
发行版
安装 gccgo 的最简单方法是安装一个 GCC 二进制发行版,该发行版构建为包含对 Go 的支持。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 编译器能够为协程使用一个小的非连续栈。这允许程序运行更多的协程,因为每个协程可以使用一个相对较小的栈。执行此操作需要使用 gold 链接器版本 2.22 或更高版本。你可以安装 GNU binutils 2.22 或更高版本,或者自己构建 gold。
要自己构建 gold,请使用 --enable-gold=default
构建 GNU binutils,当你运行 configure
脚本时。在构建之前,你必须安装 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://golang.ac.cn/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
为前缀。例如,以下是 C 函数 open
在 Go 中的声明方式
//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 声明。该功能相当尴尬,大多数用户应该改用带有 -gccgo
选项的 cgo 程序。
像往常一样编译你的 C 代码,并添加选项 -fdump-go-spec=FILENAME
。这将创建文件 FILENAME
作为编译的副作用。此文件将包含 C 代码中声明的类型、变量和函数的 Go 声明。无法在 Go 中表示的 C 类型将作为注释记录在 Go 代码中。生成的文件将没有 package
声明,但可以由 gccgo 直接编译。
该过程充满了未说明的警告和限制,我们不保证它在未来不会改变。它更适合作为编写实际 Go 代码的起点,而不是作为常规过程。