Go Slice/Map 复制陷阱

发布于:2024-04-16 ⋅ 阅读:(22) ⋅ 点赞:(0)

信的内容是我 22 年问 Google Golang Nuts 的问题以及得到的回复,原信链接:https://groups.google.com/g/golang-nuts/c/Q7_Dj_ogVJE/m/_5TSXCIaBgAJ?pli=1

信件内容

为什么Go的slice有“复制陷阱”,而map却没有?

假设我们有一个以 slice 作为输入参数的函数,如果在函数中展开 slice,则只会改变复制的 slice 结构,而不是原来的 slice 结构。原来的slice仍然指向原来的数组,而函数中的slice因为扩容而改变了数组指针。
在这里插入图片描述

看一下输出,如果slice是通过“引用”传递的,它就不会是[1 2]。再看这个例子:
在这里插入图片描述
我们在 domap() 中完成的操作成功了!

这难道是陷阱吗?!

我的意思是,map是通过“引用”(*hmap) 传递的,slice是通过“值”(SliceHeader) 传递的。

为什么map和slice被设计成都是内部引用类型,为什么这么不一致呢?

也许这只是一个设计问题,但为什么呢?为什么要这样slice和map?


以下是我对证明为什么map只能通过“引用”传递的猜测:

假设map是按值传递的-> hmap结构类型

(1)init之后:(函数外的hmap)

hmap.buckets = bucketA
hmap.oldbuckets = nil

(2)传递param后,进入函数:(函数内的hmap)

hmap.buckets = bucketA
hmap.oldbuckets = nil

(3)触发扩容后:(函数内的hmap)

hmap.buckets = bucketB
hmap.oldbuckets = bucketA

但slice不一样!

没有增量迁移,也没有oldbuckets, 所以可以使用结构体,因为函数与外部隔离,而map则不然,oldbuckets是在函数外部引用的。

我的意思是,这样设计的最初目的可能是传值,防止直接修改函数中的原始变量,但是map不能传值。一旦将值传递给map,在函数内执行扩容的过程中原始map的数据就会丢失。

如果有人能帮助我解决这个问题,我将不胜感激。多谢!

(我为我垃圾的中式英语感到抱歉,我希望我把问题说清楚了…😢这真的让我很困扰…)

golang-nuts的回信

已经翻译成中文了

@Brian Candler

(顺便说一句:请不要发布屏幕截图。纯文本更易于阅读,并且可以复制粘贴。您也可以使用 https://go.dev/play/ 粘贴代码片段)

我想你已经很好地理解了这个问题。来自 https://golang.org/src/runtime/slice.go 的源代码:

type slice struct {
      array unsafe.Pointer
      len   int
      cap   int
}

正如您所发现的,这是按值传递的。如果你愿意,当然可以显式传递指向此类值的指针。

这是陷阱吗?!

嗯,这是你必须学习的语言知识,但我认为这是可用设计选项中的最佳选择。

string 和 slice 彼此一致:它们是普通结构,包含指向数据的指针、长度和容量(对于slice)。

是否可以实现值始终是指向结构的“指针”的 slice 和 string ?

我想应该是可以的,但我认为在更深层次上你仍然会遇到类似的问题。

当复制 slice (b := a) 或将其作为函数参数传递时,这将会复制一个指针,因此同一个 slice 将有两个别名,并且对一个slice的修改将对另一个也可见。但是,当进行子切片 (b := a[1:2]) 时,将被迫分配一个新的slice结构。

因此,slice 的行为将完全取决于它的生成方式,因此改变slice可能会也可能不会影响其他slice。我认为整体上会更加混乱。

最重要的是,你仍然会遇到与今天相同的问题:

两个slice可能会也可能不会共享相同的底层缓冲区。

map和channel彼此一致:它们都有比较大的数据结构,并且值是指向该结构的指针。我认为将map值设置为指针是有充分理由的;这意味着你可以做一些方便的事情,比如:

m["foo"] = "bar"

而不是:

m = m.set("foo", "bar") // compare: s = append(s, "foo")

但另一方面,map或channel的零值并不是很有用:它是一个 nil 指针,它告诉您有零个元素,但不能添加任何元素。这使得它们在构建包含这些内容的结构时不方便,因为您需要显式地 make(…) 。这是 Go 新手抱怨的另一件事。


网站公告

今日签到

点亮在社区的每一天
去签到