Go中获取庞大结构体切片元素地址是否会触发复制?
Go切片循环中获取元素地址的行为分析
问题背景
假设Go 1.18程序中存在一个相当庞大的struct,其复制成本较高:
type MyStruct struct { P string // 大量其他属性 }
现在定义一个函数,接收该类型元素的切片作为入参,用于更新每个切片元素的属性:
func myFunc(sl []MyStruct) { for i := range sl { p := &sl[i] // <-- 此处 p.P = "bar" // 其他属性修改操作 } }
疑问点:在标记处,Go编译器是会将切片元素临时复制到循环作用域中,还是直接原地获取切片元素的地址?我们的目标是避免复制整个切片元素。
结论与解释
直接说结论:这里是原地获取切片元素的地址,不会发生整个MyStruct元素的复制,完全符合你避免复制的目标。
底层原理
切片在Go里本质是个包含底层数组指针的结构体,sl[i]直接对应底层数组里索引为i的元素。当你写&sl[i]时,编译器会直接计算该元素在底层数组里的内存地址并返回,根本不会先把元素复制到循环临时变量再取地址。
对比下另一种容易混淆的循环写法:
for _, v := range sl { // 这里的v是切片元素的副本,&v取的是副本的地址,修改v不会影响原切片元素 }
这种写法才会把每个元素完整复制到循环变量v中,而你用的for i := range sl索引遍历方式,是直接通过索引访问底层数组的元素,全程没有复制开销。
验证示例
可以通过打印地址的方式直观验证:
package main import "fmt" type MyStruct struct { P string } func myFunc(sl []MyStruct) { fmt.Println("切片遍历的元素地址:") for i := range sl { p := &sl[i] fmt.Printf("sl[%d] 地址:%p\n", i, p) p.P = "bar" } } func main() { arr := [3]MyStruct{{"foo"}, {"foo"}, {"foo"}} sl := arr[:] fmt.Println("原数组元素地址:") for i := range arr { fmt.Printf("arr[%d] 地址:%p\n", i, &arr[i]) } myFunc(sl) fmt.Println("修改后原数组内容:", arr) }
运行后会看到:sl[i]的地址和原数组arr[i]的地址完全一致,说明是直接操作底层数组的元素,没有复制;同时原数组的元素也被成功修改,进一步证明了操作的是原地元素。
内容的提问来源于stack exchange,提问作者A. Gille
相关产品推荐
相关产品推荐

