1.14 机器码生成——链接
程序可能使用其他程序或程序库(library),正如我们在helloworld程序中使用的fmt package一样,编写的程序必须与这些程序或程序库组合在一起才能执行,链接就是将编写的程序与外部程序组合在一起的过程。链接分为静态链接与动态链接,静态链接的特点是链接器会将程序中使用的所有库程序复制到最后的可执行文件中,而动态链接只会在最后的可执行文件中存储动态链接库的位置,并在运行时调用。因此静态链接更快,并且可移植,它不需要运行它的系统上存在该库,但是它会占用更多的磁盘和内存空间。静态链接发生在编译时的最后一步,动态链接发生在程序加载到内存时。表1-1对比了静态链接与动态链接的区别。
表1-1 静态链接与动态链接的区别
续表
Go语言在默认情况下是使用静态链接的,但是在一些特殊情况下,如在使用了CGO(即引用了C代码)时,则会使用操作系统的动态链接库,例如,Go语言的net/http包在默认情况下会使用libpthread与lib c的动态链接库。Go语言也支持在go build编译时通过传递参数来指定要生成的链接库的方式,可以使用go help build命令查看。
下面我们以helloworld程序为例,说明Go语言编译与链接的过程,我们可以使用go build命令,-x参数代表打印执行的过程。
由于生成的信息较长,接下来,将逐步对输出的信息进行解析。
首先创建一个临时目录,用于存放临时文件。在默认情况下,命令结束时自动删除此目录,如果需要保留则添加-work参数。
然后生成编译配置文件,主要为编译过程需要的外部依赖(如引用的其他包的函数定义)。
编译阶段会生成中间结果$WORK/b001/_pkg_.a。
.a类型的文件又叫目标文件(object file),是一个压缩包,其内部包含_.PKGDEF和go_.o两个文件,分别为编译目标文件和链接目标文件。
文件内容由导出的函数、变量及引用的其他包的信息组成。弄清这两个文件包含的信息需要查看Go语言编译器实现的相关代码,这些代码在gc/obj.go文件中。
在下面的代码中,dumpobj1函数会生成ar文件,ar文件是一种非常简单的打包文件格式,广泛用于Linux的静态链接库文件中,文件以字符串“!\n”开头,随后是60字节的文件头部(包含文件名、修改时间等信息),之后是文件内容。因为ar文件格式简单,所以Go语言编译器直接在函数中实现了ar打包过程。其中,startArchiveEntry用于预留ar文件头信息的位置(60字节),finishArchiveEntry用于写入文件头信息。因为文件头信息中包含文件大小,在写入完成之前文件大小未知,所以分两步完成。
生成链接配置文件,主要包含了需要链接的依赖项。
执行链接器,生成最终可执行文件main,同时将可执行文件复制到当前路径下并删除临时文件。