问题

运行如下代码

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)
}