Go 切片运算符是否分配新的底层数组?

Does Go slicing operator allocate new underlying array?

我正在阅读这篇文章,其中解释了 Go 中的切片是如何在底层实现的: https://medium.com/swlh/golang-tips-why-pointers-to-slices-are-useful-and-how-ignoring-them-can-lead-to-tricky-bugs-cac90f72e77b

文末是这段 Go 代码:

func main() {
    slice:= make([]string, 1, 3)
  
    func(slice []string){
        slice=slice[1:3]
        slice[0]="b"
        slice[1]="b"
        fmt.Print(len(slice))
        fmt.Print(slice)
    }(slice)
    fmt.Print(len(slice)) 
    fmt.Print(slice)
}

我的第一个猜测是这将打印:

2 [b b]3 [ b b]

事实上它打印:

2[b b]1[]

这表明,当匿名函数通过对作为参数传递给它的切片进行切片来创建新的局部切片时,会导致为该切片分配一个新的底层数组。我已经用这个修改后的代码版本确认了这一点:

func main() {
    slice := make([]string, 1, 3)

    hdr := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
    fmt.Printf("adress of underlying array in main: %p\n", unsafe.Pointer(hdr.Data))

    func(slice []string) {
        hdr := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
        fmt.Printf("adress of underlying array in func before slicing: %p\n", unsafe.Pointer(hdr.Data))
        slice = slice[1:3]
        slice[0] = "b"
        slice[1] = "b"
        hdr = (*reflect.SliceHeader)(unsafe.Pointer(&slice))
        fmt.Printf("adress of underlying array in func after slicing: %p\n", unsafe.Pointer(hdr.Data))
        fmt.Print(len(slice))
        fmt.Println(slice)
    }(slice)
    fmt.Print(len(slice))
    fmt.Println(slice)
}

打印:

adress of underlying array in main: 0xc0000121b0
adress of underlying array in func before slicing: 0xc0000121b0
adress of underlying array in func after slicing: 0xc0000121c0
2[b b]
1[]

我的问题是:为什么匿名函数中的切片操作会导致分配一个新的数组?我的理解是,如果在 main 中我们创建一个容量为 3 的切片,则会创建一个长度为 3 的底层数组,并且当匿名函数正在操作切片时,它正在操作相同的底层数组。

正如 mkopriva 提到的,重新切片不会重新分配任何东西。仅当附加新值超过切片的 容量 (source).

时才会发生重新分配。

您得到的输出是因为您在切片中“能够看到”的元素(包括打印时)取决于 its length:

The number of elements is called the length of the slice and is never negative. [...] The length of a slice s can be discovered by the built-in function len;

slice := make([]string, 1, 3)构造的原始切片,长度=1,所以当你打印它时,输出将是位置[=14=的one元素] 的支持数组,它是一个空字符串。

使用此代码:

slice = slice[1:3]
slice[0] = "b"
slice[1] = "b"

你实际上改变了后备数组 12 位置的元素,其中 none 是原始切片的一个元素。

如果您将原始切片重新切片到最大容量——从而扩展其 长度,它将打印您期望的内容:

    slice = slice[:cap(slice)]
    fmt.Println(len(slice)) // 3
    fmt.Println(slice)      // [ b b]
                            //  ^ first is empty string