为了账号安全,请及时绑定邮箱和手机立即绑定

为什么 Go 禁止取 (&) 映射成员的地址,而允许 (&) 切片元素?

为什么 Go 禁止取 (&) 映射成员的地址,而允许 (&) 切片元素?

Go
冉冉说 2021-11-08 14:36:33
Go 不允许获取地图成员的地址:// if I do this:p := &mm["abc"]// Syntax Error - cannot take the address of mm["abc"]基本原理是,如果 Go 允许获取此地址,则当 map backstore 增长或缩小时,该地址可能会变得无效,从而使用户感到困惑。但是当 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为什么 Go 是这样设计的?取切片元素的地址有什么特别之处?
查看完整描述

2 回答

?
慕莱坞森

TA贡献1810条经验 获得超4个赞

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

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

如果切片容量不足,则会创建一个新的、更大的后备阵列,并将旧的后备阵列复制到新的后备阵列中;并且旧的支持数组仍然 存在。因此,从指向旧支持数组的“未生成”切片获得的任何指针仍然是指向有效内存的有效指针。

如果您的切片仍然指向旧的支持数组(例如,因为您在切片超出其容量之前制作了切片的副本),您仍然可以访问旧的支持数组。这与切片元素的指针关系不大,但切片是数组的视图,并且在切片增长期间复制数组。

请注意,在切片收缩期间没有“减少切片的支持数组”。


查看完整回答
反对 回复 2021-11-08
?
POPMUISE

TA贡献1765条经验 获得超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] 以避免程序的微妙错误,这些错误可能会长时间保留指针而没有意识到它会指向与程序员所想的不同的数据。


查看完整回答
反对 回复 2021-11-08
  • 2 回答
  • 0 关注
  • 235 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信