最近在修复grpc-gateway 的一个 bug时,发现原作者写的测试用例根本就是无效的(总是通过)。 原因却是因为其代码“触碰”到了 Golang 中关于取地址的一个有点匪夷所思的“特性”。
问题描述
作者定义了一个具有 3 个空结构体的切片(数组),然后对这 3 个元素分别取地址,期望得到不同的地址,但是结果却是不行的。 我把原问题简化并抽象出来了(Go Playground):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
输出结果:
0x58fd18,0x58fd18,0x58fd18
0xc00009400b,0xc00009400c,0xc00009400d
具体的值不重要,重要的是:前三者相同,后三者不同。
规范是怎样的?
到了这里,我其实也是非常疑惑的。 如果仅是几个普通的结构体变量,其地址相同的话,那还可以接受。 居然,居然切片/数组中的不同元素的地址也相同?简直太不可思议了。
后来我查了语言规范(位于规范的最后一行)中关于大小和对齐的章节, 原文如是说:
A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero.
一个不包含大小大于零的字段的结构体或数组的大小是零。
Two distinct zero-size variables may have the same address in memory.
两个零大小的不同的变量在内存中可能有相同的地址。
看起来像那么回事,但是没有直接说明位于切片/数组中的元素也具体和变量类似的相同的地址。
关于这一点,Golang 在语言标准/实现上似乎跟其它语言(比如 C++)不同, C++ 明确规定:
- 空结构体的大小至少是 1
new
出来的两个新对象具有不同的地址
几年前,我在文章「一个空类/结构体的大小是多少?」中也说明过这一点。
有人利用这个特性吗?
还真有。
比如下面这个简单到无可挑剔的包 bradfitz/iter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
作者已经在注释中清楚地说明了此函数的作用及用法,以及为什么可以这样写。 注意最后那一句注释:无任何内存分配。 感觉这里可以作为一个面试考点?哈哈哈哈。
有没有人用这个包?有,还不少!
1 2 3 4 5 6 7 8 9 10 |
|
来自于:github.com/anacrolix/dht/tokens.go#L41-L50。
最后
这样设计的最原始想法是什么?