为什么我要让导出的指针变量在包级别指向未导出的变量?

iBu*_*Bug 5 pointers private public go

以标准库net/http为例,DefaultClient定义为:

var DefaultClient = &Client{}
Run Code Online (Sandbox Code Playgroud)

然而,DefaultServeMux 定义为:

var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
Run Code Online (Sandbox Code Playgroud)

为一个对象定义两个变量有什么意义?与简单的相比,它有什么好处var DefaultServeMux = &ServeMux{}

Zek*_* Lu 3

该部分是Brad FitzpatrickDefaultServeMux完成的优化。我将从CL 的评论中复制并重新格式化他和Matthew Dempsky之间的对话:

马修·登普斯基:

为了确保我理解,问题是做

var x = newFoo()
Run Code Online (Sandbox Code Playgroud)

即使 x 未使用,也需要调用 newFoo()

布拉德·菲茨帕特里克:

是的,这会生成:

var x *T

func init() {
  x = newFoo()
}
Run Code Online (Sandbox Code Playgroud)

而且链接器似乎永远不会删除初始化块,即使它们只分配给那些只能读取的东西。因为也许 newFoo 也有副作用?

马修·登普斯基:

然而

var x = &y
var y foo
Run Code Online (Sandbox Code Playgroud)

跳过显式初始化代码。因此,如果 x 未被使用,它可以与 foo 类型的所有相关代码一起被消除吗?

布拉德·菲茨帕特里克:

是的


我检查了从最新源代码(commit 4f4a9c7fff)编译的go二进制文件的大小,我发现该commit的更改有很大帮助,但var DefaultServeMux = &ServeMux{}好​​一点。见下表:

执行 go 二进制文件的大小
var DefaultServeMux = NewServeMux() 15870476
var DefaultServeMux = &defaultServeMux; var defaultServeMux ServeMux 15864704 (-5772)
var DefaultServeMux = &ServeMux{} 15864696 (-5780)

更新

我试图找出该提交创建时var DefaultServeMux = &defaultServeMux; var defaultServeMux ServeMuxvar DefaultServeMux = &ServeMux{}创建时之间的差异(回到 go1.7)。但是我的机器上编译go1.12失败,所以放弃了(参见/sf/answers/5322161351/)。我发现从 go1.7 到现在(devel go1.21-4f4a9c7fff),这两种实现对于 go 二进制文件大小的影响几乎相同。这是结果(我的环境是go1.20.3 linux/amd64,我用 编译了二进制文件./make.bash):

git标签 var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
var DefaultServeMux = &ServeMux{} 三角洲
去1.7 9966320 9966320 0
去1.8 10081258 10081258 0
去1.9 10381836 10381836 0
去1.10 11320890 11320890 0
去1.11 13032188 13032188 0
去1.12 14576719 14576719 0
去1.13 15083862 15083862 0
去1.14 15323624 15323624 0
去1.15 14272775 14272775 0
去1.16 14068974 14068982 +8
去1.17 14022237 14022237 0
去1.18 14545517 14545517 0
去1.19 15302909 15302909 0
去1.20 15579106 15579106 0
4f4a9c7fff 15864704 15864696 -8