关于指针:为什么Go禁止获取(&)地图成员的地址,但允许(&)切片元素?

Why does Go forbid taking the address of (&) map member, yet allows (&) slice element?

Go不允许获取地图成员的地址:

1
2
3
// if I do this:
p := &mm["abc"]
// Syntax Error - cannot take the address of mm["abc"]

其基本原理是,如果Go允许获取此地址,当地图后台增长或闪烁时,该地址可能会变得无效,从而混淆用户。

但是go slice在其容量超出时会被重新定位,然而go允许我们获取slice元素的地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 a := make([]Test, 5)
 a[0] = Test{1,"dsfds"}
 a[1] = Test{2,"sdfd"}
 a[2] = Test{3,"dsf"}

 addr1 := reflect.ValueOf(&a[2]).Pointer()
 fmt.Println("Address of a[2]:", addr1)

 a = append(a, Test{4,"ssdf"})
 addrx := reflect.ValueOf(&a[2]).Pointer()
 fmt.Println("Address of a[2] After Append:", addrx)

 // Note after append, the first address is invalid
 Address of a[2]:  833358258224
 Address of a[2] After Append: 833358266416

为什么Go是这样设计的?取slice元素的地址有什么特别的?


切片和映射之间有一个主要区别:切片由支持数组支持,而映射则不支持。

如果地图增大或缩小,指向地图元素的潜在指针可能会变成指向无处(非初始化内存)的悬空指针。这里的问题不是"用户的困惑",而是它会破坏go的一个主要设计元素:没有悬空的指针。

如果一个切片耗尽了容量,将创建一个新的、更大的后备数组,并将旧的后备数组复制到新的中;而旧的后备数组仍然存在。因此,从指向旧的支持数组的"unown"切片中获得的任何指针仍然是指向有效内存的有效指针。

如果有一个切片仍指向旧的备份数组(例如,因为在将切片增长到其容量之外之前复制了该切片),则仍可以访问旧的备份数组。这与切片元素的指针关系不大,但切片是数组中的视图,并且在切片增长期间复制数组。

注意,在切片收缩过程中没有"减少切片的背衬阵列"。


map和slice之间的一个基本区别是,map是一个动态数据结构,它在增长时移动所包含的值。在插入和删除操作期间,go-map的具体实现甚至可能递增一点,直到所有值都移到更大的内存结构中。因此,您可以删除一个值,突然另一个值可能会移动。另一方面,切片只是指向子数组的接口/指针。一片不长。append函数可以将一个切片复制到另一个容量更大的切片中,但它会保持旧切片的完整性,而且它也是一个函数,而不仅仅是一个索引运算符。

用地图实施者自己的话来说:


"It interferes with this growing procedure, so if I take the address
of some entry in the bucket, and then I keep that entry around for a
long time and in the meantime the map grows, then all of a sudden that
pointer points to an old bucket and not a new bucket and that pointer
is now invalid, so it's hard to provide the ability to take the
address of a value in a map, without constraining how grow works...
C++ grows in a different way, so you can take the address of a bucket"

因此,即使&m[x]可能是允许的,并且对短期操作有用(对值进行修改,然后不再使用该指针),事实上,映射在内部也会这样做,我认为语言设计者/实现者选择在映射的安全方面,而不允许&m[x]以避免与如果程序在没有意识到的情况下长时间保持指针,那么它将指向与程序员想象的不同的数据。

另请参见为什么不允许获取地图值的地址?相关评论。