前言
- 看了好久的源码,今天终于想起出一期解析,有看懂的有没看懂的,如果哪说的不对,欢迎各位大佬在评论区指出
slice源码解析
slice结构体
type slice struct {
array unsafe.Pointer //一个指向数组的指针
len int //当前容量
cap int //最大容量
}
初始化一个slice
list := make([]int,0,0)
make slice的源码
func makeslice(et *_type, len, cap int) unsafe.Pointer {
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
if overflow || mem > maxAlloc || len < 0 || len > cap {
mem, overflow := math.MulUintptr(et.size, uintptr(len))
if overflow || mem > maxAlloc || len < 0 {
panicmakeslicelen()
}
panicmakeslicecap()
}
return mallocgc(mem, et, true)
}
源码位置:sdk/runtime/slice.go:83
math.MulUintptr这个函数return了一个指针数值范围和一个是否越界,我们继续深入看看是怎么实现的
// MulUintptr returns a * b and whether the multiplication overflowed.
// On supported platforms this is an intrinsic lowered by the compiler.
func MulUintptr(a, b uintptr) (uintptr, bool) {
if a|b < 1<<(4*sys.PtrSize) || a == 0 {
return a * b, false
}
overflow := b > MaxUintptr/a
return a * b, overflow
}
这个我看了好几遍,没看明白为什么要 a|b < 2^31 (sys.PtrSize 这个其实就是8),最后想了好久,终于想通了,a|b = a+b ,如果a+b < 2^31,那么 a*b 一定小于2^62(可以满足绝大部分的情况) ,然后下面的代码就是为了兜底.
总体来说这个函数是用来判断数据大小是否越界,overflow 返回true||false.然后判断一系列条件、len是否大于cap等等,如果不满足创建条件就panic,满足就调用mallocgc(这个函数比较复杂,没太看懂,有时间单独写一期)申请内存来创建array,并返回一个创建好的array的指针
切片扩容
func growslice(et *_type, old slice, cap int) slice {
if raceenabled {
callerpc := getcallerpc()
racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
}
if msanenabled {
msanread(old.array, uintptr(old.len*int(et.size)))
}
//判断新的cap是否小于需要扩容的旧的slice的cap
if cap < old.cap {
panic(errorString("growslice: cap out of range"))
}
//判断数据的类型size是否为0,如果为0,因为是append的操作导致的扩容,
//所以不应该返回nil指针,应该返回一个空的数组
if et.size == 0 {
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
//下面这些就是判断新slice的cap了,大于1024就扩容老slice的四分之一
//否则就扩充为老slice的double
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.cap < 1024 {
newcap = doublecap
} else {
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
if newcap <= 0 {
newcap = cap
}
}
}
最上面两个if是判断是否启用数据竞争检测,可以忽略
上面这段代码主要就是来生成新slice的cap
var overflow bool
var lenmem, newlenmem, capmem uintptr
//下面这一段主要是根据数据类型的大小判断slice的容量是否越界
//然后有一段是判断2的幂来进行移位操作
switch {
case et.size == 1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
overflow = uintptr(newcap) > maxAlloc
newcap = int(capmem)
case et.size == sys.PtrSize:
lenmem = uintptr(old.len) * sys.PtrSize
newlenmem = uintptr(cap) * sys.PtrSize
capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
newcap = int(capmem / sys.PtrSize)
case isPowerOfTwo(et.size):
var shift uintptr
if sys.PtrSize == 8 {
// Mask shift for better code generation.
shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
} else {
shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
}
lenmem = uintptr(old.len) << shift
newlenmem = uintptr(cap) << shift
capmem = roundupsize(uintptr(newcap) << shift)
overflow = uintptr(newcap) > (maxAlloc >> shift)
newcap = int(capmem >> shift)
default:
lenmem = uintptr(old.len) * et.size
newlenmem = uintptr(cap) * et.size
capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
capmem = roundupsize(capmem)
newcap = int(capmem / et.size)
}
//判断是否越界
if overflow || capmem > maxAlloc {
panic(errorString("growslice: cap out of range"))
}
//还有就是这一段代码,看了好久,实在看不懂为什么要判断et.ptrdata == 0,然后清理内存中的数据
//求助旁边的大佬给我解惑了:这一段是为了判断申请的空间是在堆还是栈上,
//如果是在堆上就需要手动gc一下
//如果在栈上就不需要管了(反正函数运行结束之后会被干掉的)
//最后把老的array的数据move一下到新的array里
//再把新的slice返回出去
//至此,整个扩容就完美结束了
var p unsafe.Pointer
if et.ptrdata == 0 {
p = mallocgc(capmem, nil, false)
// 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(add(p, newlenmem), capmem-newlenmem)
} else {
// Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
p = mallocgc(capmem, et, true)
if lenmem > 0 && writeBarrier.enabled {
// Only shade the pointers in old.array since we know the destination slice p
// only contains nil pointers because it has been cleared during alloc.
bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata)
}
}
memmove(p, old.array, lenmem)
return slice{p, old.len, newcap}
}
这一块代码总体来说不算太难(除了个别的,比如内存申请等),但是大佬的思路是很强的,想跟上大佬的思路还需继续努力啊。