英文:
How to get the underlying array of a slice in Go?
问题
假设我有一个长度为3的整数数组:
nums := [3]int{1,2,3}
然后我获取只包含前两个元素的切片:
numSlice := nums[:2]
对numSlice和nums调用cap
,结果都是3,而调用len
的结果分别是2和3。
如果我在切片上追加元素(numSlice = append(numSlice, 10)
),底层数组(nums
)现在变为[1 2 10]
。cap
对于两者都保持为3,因为切片的底层数组仍然是相同的,而切片的长度现在为3。
然而,如果我再次在切片上追加元素(numSlice = append(numSlice, 20)
),切片的底层数组必须发生变化 - 当cap
现在为numSlice的两倍,长度为4时,我们可以看到这种情况。
抱歉解释得有点复杂,只是为了自己理清思路,但有人能解释一下底层数组在内部发生了什么,并且如何获取对新数组的引用吗?
英文:
Let's say I have the following array of ints of length 3:
nums := [3]int{1,2,3}
Then I grab the slice of just the first two items
numSlice := nums[:2]
Invoking cap
on numSlice and nums yields 3 in both cases, and len
yields 2 and 3 respectively.
If I then append to that slice (numSlice = append(numSlice, 10)
), the underlying array (nums
) is now [1 2 10]
. cap
remains at 3 for both, as the underlying array of the slice is the same, and len for the slice is now 3.
However, if I append to that slice again (numSlice = append(numSlice, 20)
), the underlying array of the slice must change - we see this is the case when cap
now has doubled for numSlice and len is now 4.
Sorry for the overwrought explanation, just walking myself through it, but can someone explain to me what happens under the hood to the underlying array and how to get the reference to the new array?
答案1
得分: 30
首先,如果你还没有阅读过关于切片内部的官方博客文章,你应该先读一下。那应该能解决你的问题。
现在要访问底层数组,你可以结合使用reflect
和unsafe
。特别是,reflect.SliceHeader
包含一个Data
字段,该字段包含切片的底层数组的指针。
以下是从unsafe
包的文档中改编的示例代码:
s := []int{1, 2, 3, 4}
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
data := *(*[4]int)(unsafe.Pointer(hdr.Data))
英文:
First, if you haven't already, you should read this official blog post about slice internals. That should clear up everything.
Now to access the underlying array, you can use a combination of reflect
and unsafe
. In particular, reflect.SliceHeader
contains a Data
field which contains a pointer to the underlying array of a slice.
Example adapted from the documentation of the unsafe
package:
s := []int{1, 2, 3, 4}
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
data := *(*[4]int)(unsafe.Pointer(hdr.Data))
答案2
得分: 4
只是提醒一下,回答你的第二个问题。从Go 1.17开始,你可以这样做:
(*[2]int)(numSlice)
package main
import (
"fmt"
)
func main() {
nums := [3]int{1, 2, 3}
numSlice := nums[:2]
underArr1 := (*[2]int)(numSlice)
fmt.Println(&underArr1[0]) //0xc000016018
numSlice = append(numSlice, 10)
underArr2 := (*[3]int)(numSlice)
fmt.Println(&underArr2[0]) //0xc000016018 - 相同
fmt.Println(nums) // [1 2 10]
numSlice = append(numSlice, 20)
underArr3 := (*[3]int)(numSlice)
fmt.Println(&underArr3[0]) //0xc000078030 - 不同
fmt.Println(cap(numSlice)) // 6
}
老实说,你不必转换为数组指针来查看地址,我只是这样做来回答你的第二个问题。
行为确实如你所描述的那样。当你追加 10
时,你的底层数组仍然有一个字段剩余(因为它的长度是3,但你的 numSlice 是2),即使它当前被 3
占用,它仍然可以使用,并且 3
被 10
覆盖。
当你追加 20
时,没有剩余的字段,所以它会创建一个新的底层数组(很可能是6个字段长,是原始数组的两倍),并将所有数据从原始数组复制到那里,并将指针移动到该数组。
英文:
Just as a headsup, answering your second question. Starting from Go 1.17, you can do it like this
(*[2]int)(numSlice)
package main
import (
"fmt"
)
func main() {
nums := [3]int{1, 2, 3}
numSlice := nums[:2]
underArr1 := (*[2]int)(numSlice)
fmt.Println(&underArr1[0]) //0xc000016018
numSlice = append(numSlice, 10)
underArr2 := (*[3]int)(numSlice)
fmt.Println(&underArr2[0]) //0xc000016018 - same
fmt.Println(nums) // [1 2 10]
numSlice = append(numSlice, 20)
underArr3 := (*[3]int)(numSlice)
fmt.Println(&underArr3[0]) //0xc000078030 - different
fmt.Println(cap(numSlice)) // 6
}
To be honest, you don't have to convert to array pointer to see the adresses, I just do it to answer your second question.
The behaviour is indeed the way you described it. When you append 10
, you still have one field left in your underlying array (because its length is 3, but your numSlice is 2), and even though it is currently occupied by a 3
, it can be used, and 3
is overwritten by 10
.
When you append a 20
, there are no fields left, so it creates a new underlying array (most likely 6 fields long, twice as big) and copies all the data from the original array there and moves the pointer to that array.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论