切片 slice
type slice struct {
// 指向底层数组的指针
array unsafe.Pointer
// slice引用底层数组的长度
len int
// 底层数组的长度
cap int
}
slice的创建
// 根据数组创建
arr[0:3]
slice[0:3]
// 字面量创建
slice := []int{1,2,3}
// make
slice := make([]int,3)
字面量创建切片时先创建数组再将调用runtime.newobject
创建slice
结构体
# 字面量创建
LEAQ type.[3]int(SB), AX
PCDATA $1, $0
NOP
CALL runtime.newobject(SB)
MOVQ $1, (AX)
MOVQ $2, 8(AX)
MOVQ $3, 16(AX)
make
创建切片时直接调用slice.makeslice
LEAQ type.int(SB), AX
MOVL $3, BX
MOVQ BX, CX
PCDATA $1, $0
CALL runtime.makeslice(SB)
func makeslice(et *_type, len, cap int) unsafe.Pointer {
// func math.MulUintptr(a uintptr, b uintptr) (uintptr, bool)
// MulUintptr 返回a *b 以及乘法是否溢出。 在受支持的平台上,这是编译器降低的内在属性。
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
// maxAlloc 是分配的最大大小。在 64 位上, 理论上可以分配 1< maxAlloc || len < 0 || len > cap {
//注意:产生“len out of range”错误而不是"cap out of range"
//当有人 make([]T, bignumber) 时出现“上限超出范围”错误。
//“上限超出范围”也是如此,但由于上限只是
//隐含地提供,说 len 更清楚。
mem, overflow := math.MulUintptr(et.size, uintptr(len))
if overflow || mem > maxAlloc || len < 0 {
panicmakeslicelen()
}
panicmakeslicecap()
}
// func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer
// 分配一个大小为 bytes的对象。 小对象是从 per-P 缓存的空闲列表中分配的。 大对象 (> 32 kB) 直接从堆中分配。
return mallocgc(mem, et, true)
}
示例:
arry :=[7]{0,1,2,3,4,5,6}
s := arry[1:3]
切片的追加
不扩容:只调整len(编译器负责)
扩容:编译时调用runtime.growslice()
- 放弃原来的底层数组,重新生成一个底层数组
- 期望容量大于当前容量的两倍就会使用期望容量
- 如果当前切片的长度小于256,将容量翻倍
- 如果当前切片的长度小于256,每次增加25%
- 切片扩容时,并发不安全需要加锁
//growslice 在追加期间处理切片增长。
//它传递切片元素类型、旧切片和所需的新最小容量,并返回一个至少具有该容量的新切片,并将旧数据复制到其中。
//新切片的长度设置为旧切片的长度,
//不是新请求的容量。
//这是为了方便代码生成。旧切片的长度立即用于计算在追加期间写入新值的位置。
//TODO:当旧的后端消失时,重新考虑这个决定。
//SSA 后端可能更喜欢新的长度或只返回 ptr/cap 并节省堆栈空间。
func growslice(et *_type, old slice, cap int) slice {
if raceenabled {...
}
if msanenabled {...
}
if asanenabled {...
}
if cap < old.cap {...
}
if et.size == 0 {...
}
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
const threshold = 256
if old.cap < threshold {
newcap = doublecap
} else {
//检查 0 < newcap 以检测溢出并防止无限循环。
for 0 < newcap && newcap < cap {
//从小切片增长 2 倍过渡到大切片增长 1.25 倍。这个公式给出了两者 之间的平滑过渡。
newcap += (newcap + 3*threshold) / 4
}
//当 newcap 计算溢出时,将 newcap 设置为请求的上限。
if newcap <= 0 {
newcap = cap
}
}
}
var overflow bool
var lenmem, newlenmem, capmem uintptr
//专门用于 et.size 的常见值。
//对于 1,我们不需要任何除法/乘法。
//对于 goarch.PtrSize,编译器会将除法/乘法优化为一个常数的移位。
//对于 2 的幂,使用可变移位。
switch {...
}
//除了 capmem > maxAlloc 之外,还需要检查溢出以防止溢出,该溢出可用于通过此 示例程序触发 32 位架构上的段错误:
//
// type T [1<<27 + 1]int64
//
// var d T
// var s []T
//
// func main() {
// s = append(s, d, d, d, d)
// print(len(s), "\n")
// }
if overflow || capmem > maxAlloc {
panic(errorString("growslice: cap out of range"))
}
var p unsafe.Pointer
if et.ptrdata == 0 {...
} else {...
}
memmove(p, old.array, lenmem)
return slice{p, old.len, newcap}
}