如何在Go中获取切片的底层数组?

huangapple go评论82阅读模式
英文:

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

首先,如果你还没有阅读过关于切片内部的官方博客文章,你应该先读一下。那应该能解决你的问题。

现在要访问底层数组,你可以结合使用reflectunsafe。特别是,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)

Playground

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 占用,它仍然可以使用,并且 310 覆盖。

当你追加 20 时,没有剩余的字段,所以它会创建一个新的底层数组(很可能是6个字段长,是原始数组的两倍),并将所有数据从原始数组复制到那里,并将指针移动到该数组。

英文:

Just as a headsup, answering your second question. Starting from Go 1.17, you can do it like this

(*[2]int)(numSlice)

Playground

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.

huangapple
  • 本文由 发表于 2016年4月19日 09:16:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/36706843.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定