问题
运行如下代码
package main
import (
"fmt"
)
func AddElement(slice []int, e int) []int {
return append(slice, e)
}
func main() {
var slice []int
slice = append(slice, 1, 2, 3)
newSlice := AddElement(slice, 4)
slice = append(slice, 5)
slice = append(slice, 6)
fmt.Println(newSlice)
}
运行结果:
go version 1.14
[1 2 3 5]
go version 1.16
[1 2 3 4]
很奇怪对不对???
于是乎我试了下这样子——
func main() {
var slice []int
slice = append(slice, 1, 2, 3, 4, 5, 6, 7)
newSlice := AddElement(slice, 4)
slice = append(slice, 5)
slice = append(slice, 6)
fmt.Println(newSlice)
}
这个时候两个版本的go输出的结果就都一样了
[1 2 3 4 5 6 7 5]
经过对比发现:
第一,出现这种append数据丢失的情况,往往发生在切片的 cap - len = 1的情况下,也就是说还有容量中最后一个位置可以放置元素的时候,新定义的切片进行append之后,老切片也append了,那么新定义的切片append的就会丢失。
第二,go 1.16和go 1.14在切片分配容量cap的时候,似乎有点区别,但是我看源码src/runtime/slice.go这部分的扩容机制都是一样的啊,不明白输出结果为啥不一样(求大佬明示)。
原因分析
切片是引用类型,是引用传递,newSlice 和 slice 底层用的都是同一个数组,所以newSlice进行了append之后,slice再进行append,等于修改了这个位置的元素。如下验证:可见第四个位置的地址没有变。
func main() {
var slice []int
slice = append(slice, 1, 2, 3)
fmt.Printf("%p \n", &slice) //0xc0000044a0
newSlice := append(slice, 4)
fmt.Printf("%p \n", &newSlice) // 0xc0000044e0
fmt.Printf("%p \n", &newSlice[0]) //0xc00000e380
fmt.Printf("%p \n", &newSlice[1]) //0xc00000e388
fmt.Printf("%p \n", &newSlice[2]) //0xc00000e390
fmt.Printf("%p \n", &newSlice[3]) //0xc00000e398
fmt.Println(newSlice)
slice = append(slice, 5)
fmt.Println(slice)
fmt.Printf("%p \n", &slice) // 0xc0000044a0
fmt.Printf("%p \n", &slice[0]) //0xc00000e380
fmt.Printf("%p \n", &slice[1]) //0xc00000e388
fmt.Printf("%p \n", &slice[2]) //0xc00000e390
fmt.Printf("%p \n", &slice[3]) //0xc00000e398
fmt.Println(newSlice)
}