获取切片元素的地址是否意味着 Go 中该元素的副本?

Does taking the address of a slice element implies a copy of the element in Go?

提问人:A. Gille 提问时间:11/16/2022 最后编辑:blackgreenA. Gille 更新时间:11/16/2022 访问量:223

问:

假设一个程序有一个 相当沉重的 ,对于它来说,复制被认为是昂贵的:Go 1.18struct

type MyStruct struct {
    P string
    // a lot of properties
}

现在让我们定义一个函数,将这些元素的切片作为输入参数,其目标是更新每个片元素的属性:

func myFunc(sl []MyStruct) {
    for i := range sl {
        p := &sl[i]       // <-- HERE
        p.P = "bar"
        // other properties mutations
    }
}

在标记处,Golang 编译器是在循环的作用域中临时复制 slice 元素,还是在就地获取 slice 元素的地址?<-- HERE

这个想法是避免复制整个切片元素。

一个工作示例:https://go.dev/play/p/jHOC2DauyrQ?v=goprev

Go 指针 结构 切片

评论

0赞 M-Raw 11/16/2022
如果要修改原始数组,为什么不传递数组地址?因为这样你就需要从函数返回并分配结果
3赞 icza 11/16/2022
@M-Raw 参数是一个切片,而不是一个数组,传递切片共享后备数组。修改内部的元素将在调用方处可见/可见,而无需返回切片。slmyFunc()

答:

2赞 icza 11/16/2022 #1

&sl[i]不复制 slice 元素,它只是计算到 th 个元素的地址。i

切片元素充当变量,&x 的计算结果为 x 变量的地址。想一想:既然 &sl[i] 是第 i个元素的地址,地址不需要也不使用结构值,为什么它会复制呢?

如果你的切片太大了,以至于你担心(隐式)复制对性能的影响,你真的应该首先考虑在切片中存储指针,这样你就可以使你的循环和访问元素变得更加简单,而不必担心复制:

func myFunc(sl []*MyStruct) {
    for _, v := range sl {
        v.P = "bar"
        // other properties mutations
    }
}

另请注意,如果您的切片包含非指针,并且您想要更改切片元素的字段,则索引切片并引用该字段也不涉及复制结构元素:

func myFunc(sl []MyStruct) {
    for i := range sl {
        sl[i].P = "bar"
        // other properties mutations
    }
}

是的,如果必须修改多个字段,这可能会更详细,并且效率可能更低(但编译器也可以识别和优化多个表达式的计算)。sl[i]

评论

0赞 A. Gille 11/16/2022
感谢您的回答,它证实了我对此类操作的不确定。我知道“指针切片”技巧,但并不总是可以处理指针切片而不是具体类型的元素切片。