Go与动态链接库

Go从1.5版本开始支持动态链接库。目前官方工具链仅在linux-amd64平台支持动态链接;gccgo则支持更多的平台。

在此之前,Go的所有程序都采用静态链接。一个很简单的"hello, world"程序,因为引入了fmt库(这个库进一步依赖其他代码),所以最终得到的可执行文件也比较大。如果将Go的标准库编译为动态链接库,就可以减小Go生成的可执行文件的大小。

我在Gentoo上安装了Go 1.6.3,要将标准库编译为动态链接库,需要以root权限执行

go build -buildmode=shared -linkshared std

这样就会产生/usr/lib/go/pkg/linux_amd64/dynlink/libstd.so文件。这个文件在我的机器上有32MB大,包含了Go标准库的所有内容。

Go的标准库涵盖的面其实是相当广泛的,包括归档文件格式(zip, tar)、压缩算法、图像文件格式(png等)、高精度运算、http协议等等。这些在C++的标准库中都是不包含的,需要用户安装第三方的程序库。

C++的标准库中很大一部分是算法和数据结构,例如排序、查找、堆栈、队列、搜索树、散列表等。这些是计算机科学的经典内容,C++的标准库实现得相当全面,而且基于模板的实现方式(STL)也非常精致。而Go在这些内容的设计比较粗糙,而更加关注应用层面的内容,如上一段所述。

将Go的标准库编译为动态链接库后,如果要编译其他的程序,比如

package main

import "fmt"

func main() {
	fmt.Println("hello, 世界!")
}

只需运行

go build -linkshared hello.go

即可生成可执行文件hello,它的大小在我的机器上只有16KB。如果使用静态链接,则可执行文件的大小为2.2MB,因为它包含了标准库中的目标代码。

还可以把自己编写package编译为动态链接库。不过这样做的话就会带来和目前C语言程序同样面对的相依性问题,而且操作起来有些复杂,所以并不太推荐这样做。

要编写自己的package,首先要设置GOPATH环境变量。然后创建$GOPATH/src/<pkg_name>文件夹,在其中编写package的代码。例如

package hello

import (
	"fmt"
	"io"
)

func SayHello(w io.Writer) {
	fmt.Fprintln(w, "hello, 世界!")
}

然后运行

go install -buildmode=shared -linkshared

就会把这个package安装为$GOPATH/pkg/linux_amd64_dynlink/libhello.so。编写其他依赖此库的程序时,如

package main

import (
	"hello"
	"os"
)

func main() {
	hello.SayHello(os.Stdout)
}

运行

go build -linkshared

即可动态链接刚才生成的libhello.so(以及标准库)。Linux中有一个好处,即可以在可执行文件中指定所链接的动态链接库路径(而不仅是名称),这样就不需要把各个.so文件所在的目录加入LD_LIBRARY_PATH中了。Windows中的程序,也许出于安装路径可以随意更改的原因,不支持这个功能,所加载的dll文件必须在当前目录或者PATH目录中。


分享