golang-101-hacks (14)-the association of slices and arrays

golang-101-hacks (14)-the association of slices and arrays

Note: This article is a Chinese translation of golang-101-hacks . When adding a number to a slice, if the associated array of the slice does not have enough space, a new array space will be created. At the same time, the elements in the original array are copied to the memory corresponding to this new array, and the newly added data is added to the end of the array. Therefore, when using Go's built-in append function, you need to be careful and always keep in mind the idea of โ€‹โ€‹"array may have been changed"! The following deliberately created example illustrates the points we need to pay attention to:

package main

import (
    "fmt"
)

func addTail(s []int) {
    var ns [][]int
    for _, v := range []int{1, 2} {
        ns = append(ns, append(s, v))
    }
    fmt.Println(ns)
}

func main() {
    s1 := []int{0, 0}
    s2 := append(s1, 0)
    for _, v := range [][]int{s1, s2} {
        addTail(v)
    }
}   

s1 is [0,0] s2 is [0,0,0], execute the addTail function to add 1 and 2 to the end of the slice respectively, the result we expect is:

[[0 0 1] [0 0 2]]
[[0 0 0 1] [0 0 0 2]]

But the actual output result is:

[[0 0 1] [0 0 2]]
[[0 0 0 2] [0 0 0 2]]

The running result of s1 is consistent with the expected result, but s2 is not for us to use delve to debug this problem. Check the internal mechanism of the slice addTail function to set breakpoints, and check the first execution when s1:

(dlv) n
> main.addTail() ./slice.go:8 (PC: 0x401022)
     3: import (
     4: "fmt"
     5:)
     6:
     7: func addTail(s []int) {
=> 8: var ns [][]int
     9: for _, v := range []int{1, 2} {
    10: ns = append(ns, append(s, v))
    11:}
    12: fmt.Println(ns)
    13:}
(dlv) ps
[]int len: 2, cap: 2, [0,0]
(dlv) p &s[0]
(*int)(0xc82000a2a0)

The length and capacity of s1 are both 2, and the underlying array address of the execution is 0xc82000a2a0. When ns = append(ns, append(s, v)) is executed, since the length and capacity of s1 are both 2, there is no space for storing new ones. Worth it. To add a new value, a new array must be created, which contains [0,0] in s1 and the new value (1 or 2). After executing "ns = append(ns, append(s, v))", I found:

(dlv) n
> main.addTail() ./slice.go:9 (PC: 0x401217)
     4: "fmt"
     5:)
     6:
     7: func addTail(s []int) {
     8: var ns [][]int
=> 9: for _, v := range []int{1, 2} {
    10: ns = append(ns, append(s, v))
    11:}
    12: fmt.Println(ns)
    13:}
    14:
(dlv) p ns
[][]int len: 1, cap: 1, [
        [0,0,1],
]
(dlv) p ns[0]
[]int len: 3, cap: 4, [0,0,1]
(dlv) p &ns[0][0]
(*int)(0xc82000e240)
(dlv) ps
[]int len: 2, cap: 2, [0,0]
(dlv) p &s[0]
(*int)(0xc82000a2a0)

As you can see, the length of the new slice is 3, the capacity is 4, and the underlying array address is 0xc82000e240, which is different from s1 (0xc82000a2a0). Continue executing until exit loop: We can see the length of anonymous slice is 3, capacity is 4, and the underlying array address is 0xc82000e240, different from s1's (0xc82000a2a0). Continue executing until exit loop:

(dlv) n
> main.addTail() ./slice.go:12 (PC: 0x40124c)
     7: func addTail(s []int) {
     8: var ns [][]int
     9: for _, v := range []int{1, 2} {
    10: ns = append(ns, append(s, v))
    11:}
=> 12: fmt.Println(ns)
    13:}
    14:
    15: func main() {
    16: s1 := []int{0, 0}
    17: s2 := append(s1, 0)
(dlv) p ns
[][]int len: 2, cap: 2, [
        [0,0,1],
        [0,0,2],
]
(dlv) p &ns[0][0]
(*int)(0xc82000e240)
(dlv) p &ns[1][0]
(*int)(0xc82000e280)
(dlv) p &s[0]
(*int)(0xc82000a2a0)

We can see that s1 n[0] n[1] respectively point to 3 different arrays. Now, let's follow the same steps to view s2 to see what happened:

(dlv) n
> main.addTail() ./slice.go:8 (PC: 0x401022)
     3: import (
     4: "fmt"
     5:)
     6:
     7: func addTail(s []int) {
=> 8: var ns [][]int
     9: for _, v := range []int{1, 2} {
    10: ns = append(ns, append(s, v))
    11:}
    12: fmt.Println(ns)
    13:}
(dlv) ps
[]int len: 3, cap: 4, [0,0,0]
(dlv) p &s[0]
(*int)(0xc82000e220)

The length of s2 is 3 and the capacity is 4, so there is a space to add new elements. Check the values โ€‹โ€‹of s2 and ns after executing "ns = append(ns, append(s, v))" for the first time:

(dlv)
> main.addTail() ./slice.go:9 (PC: 0x401217)
     4: "fmt"
     5:)
     6:
     7: func addTail(s []int) {
     8: var ns [][]int
=> 9: for _, v := range []int{1, 2} {
    10: ns = append(ns, append(s, v))
    11:}
    12: fmt.Println(ns)
    13:}
    14:
(dlv) p ns
[][]int len: 1, cap: 1, [
        [0,0,0,1],
]
(dlv) p &ns[0][0]
(*int)(0xc82000e220)
(dlv) ps
[]int len: 3, cap: 4, [0,0,0]
(dlv) p &s[0]
(*int)(0xc82000e220)

We can see that the array address of the new slice is also 0xc82000e220. This is because s2 has enough space for new elements and no new array needs to be allocated. Check s2 and ns again after adding 2:

(dlv)
> main.addTail() ./slice.go:12 (PC: 0x40124c)
     7: func addTail(s []int) {
     8: var ns [][]int
     9: for _, v := range []int{1, 2} {
    10: ns = append(ns, append(s, v))
    11:}
=> 12: fmt.Println(ns)
    13:}
    14:
    15: func main() {
    16: s1 := []int{0, 0}
    17: s2 := append(s1, 0)
(dlv) p ns
[][]int len: 2, cap: 2, [
        [0,0,0,2],
        [0,0,0,2],
]
(dlv) p &ns[0][0]
(*int)(0xc82000e220)
(dlv) p &ns[1][0]
(*int)(0xc82000e220)
(dlv) ps
[]int len: 3, cap: 4, [0,0,0]
(dlv) p &s[0]
(*int)(0xc82000e220)

The three slices all point to the same array, so the following value (2) will overwrite the previous item (1). In short, the append function is very tricky to handle because it can modify the underlying array without your knowledge. You must clearly understand the memory allocation of the underlying array of each slice, otherwise the slice may bring you a big surprise!

Reference: https://cloud.tencent.com/developer/article/1445770 golang-101-hacks(14)-the association between slices and arrays-Cloud + Community-Tencent Cloud