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]以避免与如果程序在没有意识到的情况下长时间保持指针,那么它将指向与程序员想象的不同的数据。
另请参见为什么不允许获取地图值的地址?相关评论。