dounaidu0204 2019-03-07 10:33
浏览 22
已采纳

Go何时分配新的支持数组进行切片?

When reading up on Go slices, I came across this behaviour in the context of the append method

If the backing array of s is too small to fit all the given values a bigger array will be allocated. The returned slice will point to the newly allocated array.
Source - Golang Tour

To understand this I wrote the following piece of code:

Try on the Go Playground

func makeSlices() {
    var a []int;

    a = append(a, 0)

    b := append(a, 1)
    printSlice("b", b)

    c := append(a, 2)
    printSlice("b", b)
    printSlice("c", c)

}

func printSlice(name string, s []int) {
    fmt.Printf("var=%v len=%d cap=%d first_address=%v %v
", name, len(s), cap(s), &s[0], s)
}

Output:

var=b len=2 cap=2 first_address=0x414020 [0 1]
var=b len=2 cap=2 first_address=0x414020 [0 2]
var=c len=2 cap=2 first_address=0x414020 [0 2]

I would expect b and c to point to the same underlying array as they both are slices of the same length


But if I were to vary the same code for another length of slice:

Try on the Go Playground

func makeSlices() {
    var a []int;

    a = append(a, 0, 9)

    d := append(a, 1, 2)
    printSlice("d", d)

    e := append(a, 3, 4)
    printSlice("d", d)
    printSlice("e", e)
}

Output:

var=d len=5 cap=8 first_address=0x450020 [0 0 9 1 2]
var=d len=5 cap=8 first_address=0x450020 [0 0 9 1 2]
var=e len=5 cap=8 first_address=0x450040 [0 0 9 3 4]

In this scenario, d and e should point to the same backing array as they are again slices of the same length but they do not.


Why this anomaly in behaviour? When exactly does Go decide to allocate a new backing array to a slice?

  • 写回答

2条回答 默认 最新

  • dpn517111 2019-03-07 10:56
    关注

    The answer is simple: append() allocates a new backing array (and copies current content over) if the elements to be appended do not fit into the current capacity. Formally:

    if len(s) + len(newElements) > cap(s) {
        // Allocate new backing array
        // copy content (s) over to new array
    } else {
        // Just resize existing slice
    }
    // append (copy) newElements
    

    So for example if len=2, cap=4, you can append 2 elements, no allocation.

    If len=2, cap=4, and you append 3 elements, then len+3 > cap, so a new backing array will be allocated (whose capacity will be greater than len+3, thinking of future growth, but its length will be 2+3=5).

    Explaining your first example

    In your first example you declare a slice variable that will have 0 length and capacity.

    var a []int
    fmt.Println(len(a), cap(a)) // Prints 0 0
    

    When you do the first append, a new array will be allocated:

    a = append(a, 0)
    fmt.Println(len(a), cap(a)) // Prints 1 2
    

    When you do another append, it fits into the capacity, so no allocation:

    fmt.Println(len(a), cap(a)) // Prints 1 2
    b := append(a, 1)
    fmt.Println(len(b), cap(b)) // Prints 2 2
    

    But this time you store the result slice in b, not in a. So if you do your 3rd append to a, that still has length=1 and cap=2, so appending another element to a won't require allocation:

    fmt.Println(len(a), cap(a)) // Prints 1 2
    c := append(a, 2)
    fmt.Println(len(c), cap(c)) // Prints 2 2
    

    So excluding the first append, all other append don't require allocation, hence the first allocated backing array is used for all a, b and c slices, hence addresses of their first elements will be the same. This is what you see.

    Explaining your second example

    Again you create an empty slice (len=0, cap=0).

    Then you do the first append: 2 elements:

    a = append(a, 0, 9)
    fmt.Println(len(a), cap(a)) // Prints 2 2
    

    This allocates a new array with length = 2, so both length and capacity of the slice will be 2.

    Then you do your 2nd append:

    d := append(a, 1, 2)
    fmt.Println(len(d), cap(d)) // Prints 4 4
    

    Since there is no room for more elements, a new array is allocated. But you store the slice pointing to this new array in d, not in a. a still points to the old array.

    Then you do your 3rd append, but to a (which points to the old array):

    fmt.Println(len(a), cap(a)) // Prints 2 2
    e := append(a, 3, 4)
    fmt.Println(len(e), cap(e)) // Prints 4 4
    

    Again, array of a can't accommodate more elements, so a new array is allocated, which you store in e.

    So d and e have different backing arrays, and appending to any slice that shares a backing array with "another" slice doesn't (can't) change this "another" slice. So the result is that you see the same address for d twice, and a different address for e.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥30 这是哪个作者做的宝宝起名网站
  • ¥60 版本过低apk如何修改可以兼容新的安卓系统
  • ¥25 由IPR导致的DRIVER_POWER_STATE_FAILURE蓝屏
  • ¥50 有数据,怎么建立模型求影响全要素生产率的因素
  • ¥50 有数据,怎么用matlab求全要素生产率
  • ¥15 TI的insta-spin例程
  • ¥15 完成下列问题完成下列问题
  • ¥15 C#算法问题, 不知道怎么处理这个数据的转换
  • ¥15 YoloV5 第三方库的版本对照问题
  • ¥15 请完成下列相关问题!