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

}

这一块代码总体来说不算太难(除了个别的,比如内存申请等),但是大佬的思路是很强的,想跟上大佬的思路还需继续努力啊。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值