为什么禁止获取(&)map成员的地址,但允许(&)切片元素?

Neo*_*ang 19 memory pointers go memory-address

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

// if I do this:
p := &mm["abc"]
// Syntax Error - cannot take the address of mm["abc"]
Run Code Online (Sandbox Code Playgroud)

理由是,如果Go允许获取此地址,则当地图反向存储增长或闪烁时,地址可能变得无效,使用户感到困惑.

但是当Go切片的容量超过它时,Go切片会被重新定位,但Go允许我们获取切片元素的地址:

 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
Run Code Online (Sandbox Code Playgroud)

为什么Go设计得像这样?获取切片元素的地址有什么特别之处?

Vol*_*ker 19

切片和贴图之间存在重大差异:切片由支持数组支持,而贴图则不支持.

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

如果切片用完容量,则会创建一个新的更大的后备阵列,并将旧的后备阵列复制到新的中; 并且旧的后备阵列仍然 存在.因此,从指向旧后备阵列的"未上"切片获得的任何指针仍然是有效存储器的有效指针.

如果您的切片仍然指向旧的后备阵列(例如,因为您在切片超出其容量之前制作了切片的副本),您仍然可以访问旧的后备阵列.这与切片元素的指针关系不大,但切片是视图到数组中,而切片在切片生长期间被复制.

请注意,在切片收缩期间没有"减少切片的背衬阵列".

  • @NeoWang:因为你传递的是对象的值来反映.如果你仍然不明白,你可以提出另一个单独的问题; 它与地图或切片无关. (2认同)

use*_*097 5

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

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

https://www.youtube.com/watch?v=Tl7mi9QmLns&feature=youtu.be&t=21m45s “它干扰了这个不断增长的过程,所以如果我获取存储桶中某个条目的地址,然后我将该条目保留在很长一段时间,同时映射增长,然后突然间该指针指向旧存储桶而不是新存储桶,并且该指针现在无效,因此很难提供获取值地址的能力一张地图,不限制增长的工作方式...... C++ 以不同的方式增长,因此您可以获取桶的地址”

因此,即使 &m[x] 可以被允许并且对短期操作很有用(对值进行修改,然后不再使用该指针),实际上映射内部是这样做的,我认为语言设计者/实现者选择使用 map 保持安全,不允许 &m[x] 以避免程序中的微妙错误,这些错误可能会长时间保留指针而没有意识到它会指向与程序员所想的不同的数据。

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