# golang slice 使用及源码分析

• 1.先做个小实验
func main(){
s1:=make([]int,0,10)
s1=[]int{1,2,3}
ss:=make([]int,0,10)
ss = s1[1:]
for i:=0;i<len(ss);i++{
ss[i] +=10
}
fmt.Println(s1)   // [1 12 13]
ss =append(ss,4)
for i:=0;i<len(ss);i++{
ss[i] +=10
}
fmt.Println(s1)  // [1 12 13] 而不是 [1,22,23]

t:=[]int{0}
printPoint(t)  // 0xc4200140a8 cap(s)= 1
t = append(t,1)
printPoint(t)  // 0xc4200140c0 0xc4200140c8 cap(s)= 2
t = append(t,2)
printPoint(t)   // 0xc4200160e0 0xc4200160e8 0xc4200160f0 cap(s)= 4
t = append(t,3)
printPoint(t)   //  0xc4200160e0 0xc4200160e8 0xc4200160f0 0xc4200160f8 cap(s)= 4
}

func printPoint(s []int){
for i:=0;i<len(s);i++{
fmt.Print(unsafe.Pointer(&s[i])," ")

}
fmt.Println("cap(s)=",cap(s))
}

• 2.在runtime目录下找到slice.go,定位到growslice(et *_type, old slice, cap int)这个函数
type slice struct {
array unsafe.Pointer
len   int
cap   int
}

// growslice handles slice growth during append.
// It is passed the slice element type, the old slice, and the desired new minimum capacity,
// and it returns a new slice with at least that capacity, with the old data
// copied into it.
// The new slice's length is set to the old slice's length,
// NOT to the new requested capacity.
// This is for codegen convenience. The old slice's length is used immediately
// to calculate where to write new values during an append.
// TODO: When the old backend is gone, reconsider this decision.
// The SSA backend might prefer the new length or to return only ptr/cap and save stack space.

// 与append(slice,s)对应的函数growslice
// 通过切片的类型，旧切片的容量和数据得出新切片的容量，新切片跟据容量重新申请一块地址，把旧切片的数据拷贝到新切片中

func growslice(et *_type, old slice, cap int) slice {

// 单纯地扩容，不写数据
if et.size == 0 {
if cap < old.cap {
panic(errorString("growslice: cap out of range"))
}
// append should not create a slice with nil pointer but non-zero len.
// We assume that append doesn't need to preserve old.array in this case.
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
// 扩容规则 1.新的容量大于旧的2倍，直接扩容至新的容量
// 2.新的容量不大于旧的2倍，当旧的长度小于1024时，扩容至旧的2倍，否则扩容至旧的5/4倍
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
for newcap < cap {
newcap += newcap / 4
}
}
}

// 跟据切片类型和容量计算要分配内存的大小
var lenmem, newlenmem, capmem uintptr
const ptrSize = unsafe.Sizeof((*byte)(nil))
switch et.size {
case 1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
newcap = int(capmem)
case ptrSize:
lenmem = uintptr(old.len) * ptrSize
newlenmem = uintptr(cap) * ptrSize
capmem = roundupsize(uintptr(newcap) * ptrSize)
newcap = int(capmem / ptrSize)
default:
lenmem = uintptr(old.len) * et.size
newlenmem = uintptr(cap) * et.size
capmem = roundupsize(uintptr(newcap) * et.size)
newcap = int(capmem / et.size)
}

// 异常情况，旧的容量比新的容量还大或者新的容量超过限制了
if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {
panic(errorString("growslice: cap out of range"))
}

var p unsafe.Pointer
if et.kind&kindNoPointers != 0 {

// 为新的切片开辟容量为capmem的地址空间
p = mallocgc(capmem, nil, false)
// 将旧切片的数据搬到新切片开辟的地址中
memmove(p, old.array, lenmem)
// The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length).
// Only clear the part that will not be overwritten.
// 清理下新切片中剩余地址，不能存放堆栈指针

// memclrNoHeapPointers clears n bytes starting at ptr.
//
// Usually you should use typedmemclr. memclrNoHeapPointers should be
// used only when the caller knows that *ptr contains no heap pointers
// because either:
//
// 1. *ptr is initialized memory and its type is pointer-free.
//
// 2. *ptr is uninitialized memory (e.g., memory that's being reused
//    for a new allocation) and hence contains only "junk".
} else {
// Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
p = mallocgc(capmem, et, true)
if !writeBarrier.enabled {
memmove(p, old.array, lenmem)
} else {
for i := uintptr(0); i < lenmem; i += et.size {
}
}
}

return slice{p, old.len, newcap}
}

•  3.slice作为函数参数
func main(){
s:=make([]int,0,5)
s=append(s,1,2,3,4)
printPoint(s)   // 1 0xc420018120 2 0xc420018128 3 0xc420018130 4 0xc420018138 cap(s)= 5 &s= 0xc42000a060
processSlice(s)  //11 0xc420018120 12 0xc420018128 13 0xc420018130 14 0xc420018138 11 0xc420018140 cap(s)= 5 &s= 0xc42000a080
}

func processSlice(ss []int){
for i:=0;i<len(ss);i++{
ss[i] +=10
}
ss=append(ss,11)
printPoint(ss)
}
func printPoint(s []int){
for i:=0;i<len(s);i++{
fmt.Print(s[i],unsafe.Pointer(&s[i])," ")

}
fmt.Println("cap(s)=",cap(s),"&s=",unsafe.Pointer(&s))
}

函数中的形参slice是实参的拷贝，指向切片的指针不同,由于sice没有扩容，函数里面的slice和主函数的实参slice指向的数组地址是一样的

func main(){
s:=make([]int,0,4)
s=append(s,1,2,3,4)
printPoint(s)  // 1 0xc42008c000 2 0xc42008c008 3 0xc42008c010 4 0xc42008c018 cap(s)= 4 &s= 0xc42008a020
processSlice(s) // 11 0xc420092000 12 0xc420092008 13 0xc420092010 14 0xc420092018 11 0xc420092020 cap(s)= 8 &s= 0xc42008a040

}

func processSlice(ss []int){
for i:=0;i<len(ss);i++{
ss[i] +=10
}
ss=append(ss,11)
printPoint(ss)
}
func printPoint(s []int){
for i:=0;i<len(s);i++{
fmt.Print(s[i],unsafe.Pointer(&s[i])," ")

}
fmt.Println("cap(s)=",cap(s),"&s=",unsafe.Pointer(&s))
}

• 4.总结
1. 不要轻易的对切片append，如果新的切片容量比旧的大的话，需要进行growslice操作，新的地址开辟，数据拷贝
2. 尽量对切片设置初始容量值以避免growslice，类似make([]int,0,100)
3. 切片是一个结构体，保存着切片的容量，实际长度以及数组的地址
4. 切片作为函数参数传入会进行引用拷贝，生成一个新的切片，指向同一个数组
posted @ 2018-07-16 15:13  孤独风中一匹狼  阅读(5886)  评论(0编辑  收藏  举报