大家好,我是煎鱼。
在编写 Go 应用程序时,Go 本身提供了跨平台编译,提供了非常大的便利。但内部其实有许多静态和动态链接的相关知识点。
今天给大家分享这一块的基本知识。
如何选择?Go 团队的讨论
Go 核心团队在创造这门编程语言时已经做了大量的讨论和权衡。
图片
“静态链接有很多优点。部署简单是其中之一。没有版本问题是另一个优点(升级可能永远不会破坏您的 go 二进制文件。但动态链接或解释语言则不然,因为依赖关系可能会中断)。启动时间更快也是另一个优点。
不过,动态链接也有很好的理由。真实原因是:因为 go 作者已经研究了静态链接与动态链接的权衡,并决定静态链接更适合他们的用例。而且 go 社区中的大多数人都同意这一点。”
静态和动态链接是什么?
静态和动态链接是两种不同的程序链接方式,一般会根据实际的程序运行情况进行选择。
图片
静态链接
在编译时,链接器将程序所需的所有库文件直接复制到可执行文件中,生成一个完整的可执行文件。
一旦生成后,执行时不再依赖外部库。
- 优点:简单且不需要额外的库文件。
- 缺点:可执行文件较大,更新库时需要重新编译。
动态链接
在程序运行时,操作系统根据需要加载共享库到内存中。
这使得可执行文件更小,因为它不包含所有的库代码,但程序执行时依赖于外部库的存在。
- 优点:在于节省内存和便于库的更新。
- 缺点:可能存在一些版本兼容性问题等。
快速例子
静态链接
对于 Go 这一门编程语言而言,静态链接是他大力宣传的一个招牌:只需要编译一个二进制文件,哪里都可以部署。
示例代码如下:
package main
import (
"fmt"
)
func main() {
fmt.Println("脑子进煎鱼了!")
}
输出结果:
脑子进煎鱼了!
强制指定 CGO_ENABLED=0 来进行编译:
CGO_ENABLED=0 go build main.go
使用 file 工具查看目标文件信息:
$ file greet
greet: Mach-O 64-bit executable arm64
结合查看 fmt.Println 是否固定地址:
图片
结合来看,可以确定 Go 程序本身的依赖是静态链接的。而编译出来的二进制程序到底有没有动态链接库,取决于你所编写的程序。
动态链接
Go 应用程序在进行 go build 的时候,可以加入 -buildmode 参数来指定构建模式,以此实现动态链接的目的。
以下是可用的 buildmode 选项:
- archive: 将非 main 包构建成 .a 文件,main 包将被排除。
- c-archive: 构建 main 包及其依赖的所有包为 C 归档文件。
- c-shared: 构建指定的 main 包及其所有依赖为 C 动态库。
- shared: 将所有非 main 包整合到一个动态库中。
- exe: 构建指定的 main 包及其依赖为可执行文件,未命名为 main 的包将被忽略。
默认情况下,main 包会被内置到可执行文件中,非 main 包则会被内置到 .a 文件中。
这块我们写 Web 程序的用的不多,如果是写 C 库或者调第三方语言的动态库时用得多。此时需要在编译时指定 CGO_ENABLED=1 才可以。
有兴趣的话,CGO 例子可以参考:andreiavrammsd/cgo-examples[1],这里不展开。
总结
今天我们快速的介绍了 Go 语言中的静态和动态链接的基本概念,打了个底。静态链接会实现 ALL IN ONE 的效果,确保编译出来的二进制文件能够在标准的环境下运行。而动态链接则相反。
这是 Go 这门编程语言设计时的一个招牌特性,而我们做 Web 开发的话,一般都是以静态链接为主。
如果有涉及到第三方调用才会用到动态链接等。我见过用 CGO 逐步重构 C 服务的,甚至要慎防内存泄露。这时候又是另外一套逻辑、体系了,要进一步进修!