一尘不染

为什么Go禁止使用(&)映射成员的地址,却允许(&)slice元素?

go

Go不允许使用地图成员的地址:

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

理由是,如果Go允许使用此地址,则当地图后备存储增长或减少时,该地址可能变得无效,从而使用户感到困惑。

但是,当Go slice超出其容量时,它将被重新定位,但是Go允许我们获取slice元素的地址:

 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这样设计?切片元素的地址有何特殊之处?


阅读 314

收藏
2020-07-02

共1个答案

一尘不染

切片和贴图之间的主要区别在于:切片由后备数组支持,而贴图则没有。

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

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

如果您有一个切片仍指向旧的支持阵列(例如,因为在将切片增长到超出其容量之前已复制了该切片),您仍将访问旧的支持阵列。这与切片元素的指针关系不大,但是切片是视图到数组中,并且数组在切片增长期间被复制。

注意,在切片收缩期间没有“减少切片的衬背阵列”。

2020-07-02