分析go中slice的奇怪现象

问题描述

片段一:

s := []byte("")
s1 := append(s,'a')
s2 := append(s,'b')
fmt.Println(s1,"=====",s2) // [97] ===== [98]
fmt.Println(string(s1),"======",string(s2)) // a ====== b
复制代码

片段二:

s := []byte("")
s1 := append(s,'a')
s2 := append(s,'b')
fmt.Println(string(s1),"======",string(s2)) // b ====== b
复制代码

可以看到,片段一和片段二中s1和s2输出不一致。

问题分析

初看起来,感觉是fmt.Println(s1,"=====",s2)这句话导致了结果的不一样。

具体原因,且看下面分解。

对于片段二,结果都是b,这个似乎是因为append的时候,赋值给了一个新的变量,导致了s指向的底层数据虽然改变了,但是s记录的len还是不变的,所以第二次append的时候,就把第一次的值给覆盖了。所以才得出了都是b的结果。

对于问题二的解释,其实还少考虑了一个问题,那就是如果在append的时候,底层数组重新分配了,那么,就不会出现这个问题了。不信,看下面这个例子:

s := []int{5, 7, 9}
fmt.Printf("%d, %d, %p\n", len(s), cap(s), &s[0]) // 3, 3, 0xc000016240
x := append(s, 11)
fmt.Printf("%d, %d, %p\n", len(x), cap(x), &x[0]) // 4, 6, 0xc0000141e0
y := append(s, 12)
fmt.Printf("%d, %d, %p\n", len(y), cap(y), &y[0]) // 4, 6, 0xc000014210
fmt.Println(s, x, y) // [5 7 9] [5 7 9 11] [5 7 9 12]
复制代码

从上面例子可以看到,一开始s的len是3, cap也是3,在第一次append的时候,底层数组需要扩容,翻倍为6,所以x的len是4, cap是6,此时指向的底层数组地址已经不同了,第二个append同理。所以两次append,因为底层数据扩容的原因,两次append作用的地方是不同的,导致第二次没有覆盖第一次的数据。

通过上面的解释,我们知道了第一个片段出现的原因就是因为两次append都扩容了,所以没有出现覆盖的现象。而第二个片段没有出现扩容的情况,所以就出现了覆盖的情况。

那么为什么会这样呢,我们尝试打印一下,初始化s的cap,可以发现片段一中初始化s的cap为0,片段二为32。具体原因可以看下面的逃逸分析

逃逸分析

使用下面的命令进行分析。

go tool compile -m main.go
复制代码

片段一:

main.go:9:13: s1 escapes to heap
main.go:6:13: ([]byte)("") escapes to heap
main.go:9:17: "=====" escapes to heap
main.go:9:17: s2 escapes to heap
main.go:10:20: string(s1) escapes to heap
main.go:10:20: string(s1) escapes to heap
main.go:10:25: "======" escapes to heap
main.go:10:40: string(s2) escapes to heap
main.go:10:40: string(s2) escapes to heap
main.go:9:13: main ... argument does not escape
main.go:10:13: main ... argument does not escape
复制代码

可以看到s1s2由于Println,逃逸到了heap上,所以s一开始的cap是0.

片段二:

main.go:9:20: string(s1) escapes to heap
main.go:9:20: string(s1) escapes to heap
main.go:9:25: "======" escapes to heap
main.go:9:40: string(s2) escapes to heap
main.go:9:40: string(s2) escapes to heap
main.go:6:13: main ([]byte)("") does not escape
main.go:9:13: main ... argument does not escape
复制代码

可以看到s1s2没有逃逸,还是在栈上,所以s一开始的cap是32(默认).

猜你喜欢

转载自juejin.im/post/5bc457b3e51d450e4369a53d