從源碼的角度去學(xué)習(xí) Go slice
點擊上方“Go編程時光”,選擇“加為星標(biāo)”
第一時間關(guān)注Go技術(shù)干貨!

作者:吳國華
原文:https://www.kevinwu0904.top/blogs/golang-slice/
slice是golang開發(fā)中最常用到的內(nèi)置類型之一。與數(shù)組相比,它具有長度不固定、可動態(tài)添加元素的特性。
# 版本說明
本文涉及到的源碼解析部分來源于go官方的1.15.8版本。
# 定義
slice在golang的runtime層面定義如下:
type?slice?struct?{
????array?unsafe.Pointer?//?數(shù)組元素
????len???int?//?長度
????cap???int?//?容量
}
slice包括3個字段:
array:表示當(dāng)前slice存放的數(shù)組元素。
len:表示當(dāng)前slice已經(jīng)使用的長度。
cap:表示當(dāng)前slice的總?cè)萘浚醋畲竽軌蛉菁{的元素個數(shù)。

如圖所示,slice的動態(tài)特性正是基于這樣的數(shù)據(jù)結(jié)構(gòu):一方面,記錄已經(jīng)使用的元素個數(shù);另一方面,元素存放在有容量上限的底層數(shù)組中。
# 創(chuàng)建slice
slice的創(chuàng)建需要借助golang builtin包中的make函數(shù):
//?The?make?built-in?function?allocates?and?initializes?an?object?of?type
//?slice,?map,?or?chan?(only).?Like?new,?the?first?argument?is?a?type,?not?a
//?value.?Unlike?new,?make's?return?type?is?the?same?as?the?type?of?its
//?argument,?not?a?pointer?to?it.?The?specification?of?the?result?depends?on
//?the?type:
//????Slice:?The?size?specifies?the?length.?The?capacity?of?the?slice?is
//????equal?to?its?length.?A?second?integer?argument?may?be?provided?to
//????specify?a?different?capacity;?it?must?be?no?smaller?than?the
//????length.?For?example,?make([]int,?0,?10)?allocates?an?underlying?array
//????of?size?10?and?returns?a?slice?of?length?0?and?capacity?10?that?is
//????backed?by?this?underlying?array.
//????Map:?An?empty?map?is?allocated?with?enough?space?to?hold?the
//????specified?number?of?elements.?The?size?may?be?omitted,?in?which?case
//????a?small?starting?size?is?allocated.
//????Channel:?The?channel's?buffer?is?initialized?with?the?specified
//????buffer?capacity.?If?zero,?or?the?size?is?omitted,?the?channel?is
//????unbuffered.
func?make(t?Type,?size?...IntegerType)?Type
make在golang中是一個創(chuàng)建內(nèi)置類型的通用函數(shù),不僅僅是slice,包括chan、map都需要借助make函數(shù)來創(chuàng)建。slice的創(chuàng)建主要包括如下兩種形式:
nums?:=?make([]int,?10)?//?創(chuàng)建長度和容量均為10的int?slice
nums?:=?make([]int,?0,?10)?//?創(chuàng)建長度為0,容量為10的int?slice
以下面的代碼為例:
package?main
import?(
????"fmt"
)
func?main()?{
????nums?:=?make([]int,?0,?10)
????nums?=?append(nums,?0)
????fmt.Println(nums)
}
讓我們通過go的官方工具來查看匯編結(jié)果:
$?go?tool?compile?-S?main.go
"".main?STEXT?size=217?args=0x0?locals=0x58?funcid=0x0
????0x0000?00000?(main.go:7)????TEXT????"".main(SB),?ABIInternal,?$88-0
????0x0000?00000?(main.go:7)????MOVQ????(TLS),?CX
????0x0009?00009?(main.go:7)????CMPQ????SP,?16(CX)
????0x000d?00013?(main.go:7)????PCDATA??$0,?$-2
????0x000d?00013?(main.go:7)????JLS?207
????0x0013?00019?(main.go:7)????PCDATA??$0,?$-1
????0x0013?00019?(main.go:7)????SUBQ????$88,?SP
????0x0017?00023?(main.go:7)????MOVQ????BP,?80(SP)
????0x001c?00028?(main.go:7)????LEAQ????80(SP),?BP
????0x0021?00033?(main.go:7)????FUNCDATA????$0,?gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
????0x0021?00033?(main.go:7)????FUNCDATA????$1,?gclocals·f207267fbf96a0178e8758c6e3e0ce28(SB)
????0x0021?00033?(main.go:7)????FUNCDATA????$2,?"".main.stkobj(SB)
????0x0021?00033?(main.go:8)????LEAQ????type.int(SB),?AX
????0x0028?00040?(main.go:8)????MOVQ????AX,?(SP)
????0x002c?00044?(main.go:8)????MOVQ????$0,?8(SP)
????0x0035?00053?(main.go:8)????MOVQ????$10,?16(SP)
????0x003e?00062?(main.go:8)????PCDATA??$1,?$0
????0x003e?00062?(main.go:8)????NOP
????0x0040?00064?(main.go:8)????CALL????runtime.makeslice(SB)?#?從這一行我們可以看出slice的創(chuàng)建最終底層實現(xiàn)是runtime.makeslice函數(shù)
????0x0045?00069?(main.go:8)????MOVQ????24(SP),?AX
????0x004a?00074?(main.go:9)????MOVQ????$0,?(AX)
????0x0051?00081?(main.go:10)???MOVQ????AX,?(SP)
????0x0055?00085?(main.go:10)???MOVQ????$1,?8(SP)
????0x005e?00094?(main.go:10)???MOVQ????$10,?16(SP)
????0x0067?00103?(main.go:10)???CALL????runtime.convTslice(SB)
????0x006c?00108?(main.go:10)???MOVQ????24(SP),?AX
????0x0071?00113?(main.go:10)???XORPS???X0,?X0
????0x0074?00116?(main.go:10)???MOVUPS??X0,?""..autotmp_13+64(SP)
????0x0079?00121?(main.go:10)???LEAQ????type.[]int(SB),?CX
????0x0080?00128?(main.go:10)???MOVQ????CX,?""..autotmp_13+64(SP)
????0x0085?00133?(main.go:10)???MOVQ????AX,?""..autotmp_13+72(SP)
????...?省略不相關(guān)部分
PS:請讀者先不要迷失在匯編代碼的細(xì)節(jié)中,目前我們只需要關(guān)注"(main.go:8)“對應(yīng)底層實現(xiàn)是調(diào)用go的runtime.makeslice函數(shù),了解這一點即可。
因此,我們進(jìn)一步去翻閱相關(guān)的go源碼:
func?makeslice(et?*_type,?len,?cap?int)?unsafe.Pointer?{?//?_type是go所有類型在runtime層面的表示形式
????mem,?overflow?:=?math.MulUintptr(et.size,?uintptr(cap))?//?預(yù)期內(nèi)存開銷是:元素類型長度x元素個數(shù),同時判斷是否算術(shù)溢出
????if?overflow?||?mem?>?maxAlloc?||?len?0?||?len?>?cap?{
????????//?NOTE:?Produce?a?'len?out?of?range'?error?instead?of?a
????????//?'cap?out?of?range'?error?when?someone?does?make([]T,?bignumber).
????????//?'cap?out?of?range'?is?true?too,?but?since?the?cap?is?only?being
????????//?supplied?implicitly,?saying?len?is?clearer.
????????//?See?golang.org/issue/4085.
????????mem,?overflow?:=?math.MulUintptr(et.size,?uintptr(len))
????????if?overflow?||?mem?>?maxAlloc?||?len?0?{
????????????panicmakeslicelen()
????????}
????????panicmakeslicecap()
????}
????return?mallocgc(mem,?et,?true)?//?申請內(nèi)存塊
}
makeslice函數(shù)計算出所需的內(nèi)存開銷之后,會檢查是否算術(shù)溢出,緊接著通過mallocgc函數(shù)去申請內(nèi)存塊。PS:由于mallocgc涉及到比較多的go內(nèi)存管理相關(guān)的知識點,因此不在本文展開。
至此,slice就完成了內(nèi)存申請。
# 復(fù)制slice
slice的復(fù)制可以借助golang builtin包中的copy函數(shù):
//?The?copy?built-in?function?copies?elements?from?a?source?slice?into?a
//?destination?slice.?(As?a?special?case,?it?also?will?copy?bytes?from?a
//?string?to?a?slice?of?bytes.)?The?source?and?destination?may?overlap.?Copy
//?returns?the?number?of?elements?copied,?which?will?be?the?minimum?of
//?len(src)?and?len(dst).
func?copy(dst,?src?[]Type)?int
以下面的代碼為例:
package?main
import?(
????"fmt"
)
func?main()?{
????src?:=?[]int{?1,?2,?3?}
????tar?:=?make([]int,?2)
????copy(tar,?src)
????fmt.Println(tar)
}
讓我們通過go的官方工具來查看匯編結(jié)果:
$?go?tool?compile?-S?main.go"".main?STEXT?size=261?args=0x0?locals=0x70?funcid=0x0????0x0000?00000?(main.go:7)????TEXT????"".main(SB),?ABIInternal,?$112-0???0x0000?00000?(main.go:7)????MOVQ????(TLS),?CX???0x0009?00009?(main.go:7)????CMPQ????SP,?16(CX)??0x000d?00013?(main.go:7)????PCDATA??$0,?$-2???0x000d?00013?(main.go:7)????JLS?249?0x0013?00019?(main.go:7)????PCDATA??$0,?$-1???0x0013?00019?(main.go:7)????SUBQ????$112,?SP???0x0017?00023?(main.go:7)????MOVQ????BP,?104(SP)?0x001c?00028?(main.go:7)????LEAQ????104(SP),?BP?0x0021?00033?(main.go:7)????FUNCDATA????$0,?gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)??0x0021?00033?(main.go:7)????FUNCDATA????$1,?gclocals·f207267fbf96a0178e8758c6e3e0ce28(SB)??0x0021?00033?(main.go:7)????FUNCDATA????$2,?"".main.stkobj(SB)?0x0021?00033?(main.go:8)????MOVQ????$0,?""..autotmp_12+64(SP)??0x002a?00042?(main.go:8)????XORPS???X0,?X0??0x002d?00045?(main.go:8)????MOVUPS??X0,?""..autotmp_12+72(SP)???0x0032?00050?(main.go:8)????MOVQ????$1,?""..autotmp_12+64(SP)??0x003b?00059?(main.go:8)????MOVQ????$2,?""..autotmp_12+72(SP)??0x0044?00068?(main.go:8)????MOVQ????$3,?""..autotmp_12+80(SP)??0x004d?00077?(main.go:9)????LEAQ????type.int(SB),?AX????0x0054?00084?(main.go:9)????MOVQ????AX,?(SP)????0x0058?00088?(main.go:9)????MOVQ????$2,?8(SP)??0x0061?00097?(main.go:9)????MOVQ????$3,?16(SP)?0x006a?00106?(main.go:9)????LEAQ????""..autotmp_12+64(SP),?AX???0x006f?00111?(main.go:9)????MOVQ????AX,?24(SP)??0x0074?00116?(main.go:9)????PCDATA??$1,?$0????0x0074?00116?(main.go:9)????CALL????runtime.makeslicecopy(SB)?#?調(diào)用runtime的makeslicecopy函數(shù)???0x0079?00121?(main.go:9)????MOVQ????32(SP),?AX??0x007e?00126?(main.go:11)???MOVQ????AX,?(SP)????0x0082?00130?(main.go:11)???MOVQ????$2,?8(SP)??0x008b?00139?(main.go:11)???MOVQ????$2,?16(SP)?0x0094?00148?(main.go:11)???CALL????runtime.convTslice(SB)??0x0099?00153?(main.go:11)???MOVQ????24(SP),?AX??0x009e?00158?(main.go:11)???XORPS???X0,?X0??0x00a1?00161?(main.go:11)???MOVUPS??X0,?""..autotmp_17+88(SP)???0x00a6?00166?(main.go:11)???LEAQ????type.[]int(SB),?CX??0x00ad?00173?(main.go:11)???MOVQ????CX,?""..autotmp_17+88(SP)???0x00b2?00178?(main.go:11)???MOVQ????AX,?""..autotmp_17+96(SP)???...?省略不相關(guān)部分
通過匯編代碼的結(jié)果可以看出,copy函數(shù)在go的runtime層面是調(diào)用了runtime.makeslicecopy函數(shù)。因此,我們進(jìn)一步查看相關(guān)源碼:
//?makeslicecopy?allocates?a?slice?of?"tolen"?elements?of?type?"et",
//?then?copies?"fromlen"?elements?of?type?"et"?into?that?new?allocation?from?"from".
func?makeslicecopy(et?*_type,?tolen?int,?fromlen?int,?from?unsafe.Pointer)?unsafe.Pointer?{?//?參數(shù)分別是:數(shù)據(jù)類型、目標(biāo)長度、來源長度、來源元素數(shù)組
????/*
????????計算需要復(fù)制的內(nèi)存塊大小:
????????1.?目標(biāo)slice長度?>?來源slice長度,以來源長度為準(zhǔn)
????????2.?目標(biāo)slice長度?<=?來源slice長度,以目標(biāo)長度為準(zhǔn)
????*/
????var?tomem,?copymem?uintptr
????if?uintptr(tolen)?>?uintptr(fromlen)?{
????????var?overflow?bool
????????tomem,?overflow?=?math.MulUintptr(et.size,?uintptr(tolen))
????????if?overflow?||?tomem?>?maxAlloc?||?tolen?0?{
????????????panicmakeslicelen()
????????}
????????copymem?=?et.size?*?uintptr(fromlen)
????}?else?{
????????//?fromlen?is?a?known?good?length?providing?and?equal?or?greater?than?tolen,
????????//?thereby?making?tolen?a?good?slice?length?too?as?from?and?to?slices?have?the
????????//?same?element?width.
????????tomem?=?et.size?*?uintptr(tolen)
????????copymem?=?tomem
????}
????/*
????????申請內(nèi)存塊
????*/?
????var?to?unsafe.Pointer
????if?et.ptrdata?==?0?{
????????to?=?mallocgc(tomem,?nil,?false)
????????if?copymem?????????????memclrNoHeapPointers(add(to,?copymem),?tomem-copymem)
????????}
????}?else?{
????????//?Note:?can't?use?rawmem?(which?avoids?zeroing?of?memory),?because?then?GC?can?scan?uninitialized?memory.
????????to?=?mallocgc(tomem,?et,?true)
????????if?copymem?>?0?&&?writeBarrier.enabled?{
????????????//?Only?shade?the?pointers?in?old.array?since?we?know?the?destination?slice?to
????????????//?only?contains?nil?pointers?because?it?has?been?cleared?during?alloc.
????????????bulkBarrierPreWriteSrcOnly(uintptr(to),?uintptr(from),?copymem)
????????}
????}
????/*
????????數(shù)據(jù)競爭檢測和支持與內(nèi)存清理程序的互操作
????*/
????if?raceenabled?{
????????callerpc?:=?getcallerpc()
????????pc?:=?funcPC(makeslicecopy)
????????racereadrangepc(from,?copymem,?callerpc,?pc)
????}
????if?msanenabled?{
????????msanread(from,?copymem)
????}
????//?復(fù)制內(nèi)存塊
????memmove(to,?from,?copymem)
????return?to
}
小結(jié)一下,通過copy函數(shù)的底層實現(xiàn),我們能夠知道:
slice的復(fù)制是基于復(fù)制內(nèi)存塊來完成的。
slice的復(fù)制會考慮目標(biāo)slice的長度,僅會復(fù)制不大于目標(biāo)slice長度的元素。
# 新增元素
給slice新增元素需要借助golang builtin包中的append函數(shù):
//?The?append?built-in?function?appends?elements?to?the?end?of?a?slice.?If//?it?has?sufficient?capacity,?the?destination?is?resliced?to?accommodate?the//?new?elements.?If?it?does?not,?a?new?underlying?array?will?be?allocated.//?Append?returns?the?updated?slice.?It?is?therefore?necessary?to?store?the//?result?of?append,?often?in?the?variable?holding?the?slice?itself://????slice?=?append(slice,?elem1,?elem2)//???slice?=?append(slice,?anotherSlice...)//?As?a?special?case,?it?is?legal?to?append?a?string?to?a?byte?slice,?like?this://????slice?=?append([]byte("hello?"),?"world"...)func?append(slice?[]Type,?elems?...Type)?[]Type
日常使用示例如下:
nums?:=?make([]int,?0,?10)
nums?=?append(nums,?1)
nums?=?append(nums,?5)
nums?=?append(nums,?6)
# 刪除元素
不同于新增元素,golang中并未內(nèi)置刪除slice指定元素的函數(shù),需要開發(fā)者自己去實現(xiàn)。就筆者所了解的刪除元素方法主要有如下幾種方式:
方法一:創(chuàng)建新的slice切片
func?removeElem(src?[]int,?toDel?int)?[]int?{
????tar?:=?make([]int,?0,?len(src))
????for?_,?num?:=?range?src?{
????????if?num?!=?toDel?{
????????????tar?=?append(tar,?num)
????????}
????}
????return?tar
}
方法二:復(fù)用原有的slice切片
func?removeElem(src?[]int,?toDel?int)?[]int?{
????tar?:=?src[:0]
????for?_,?num?:=?range?src?{
????????if?num?!=?toDel?{
????????????tar?=?append(tar,?num)
????????}
????}
????return?tar
}
方法三:復(fù)用原有的slice切片,平移剩下的元素
func?removeElem(src?[]int,?toDel?int)?[]int?{
????for?i?:=?0;?i?len(src);?i++?{
????????if?src[i]?==?toDel?{
????????????src?=?append(src[:i],?src[i+1:]...)
????????????i--
????????}
????}
????return?src
}
小結(jié)一下,方法一的主要優(yōu)點是不修改原有slice的數(shù)據(jù),僅當(dāng)不允許修改源slice數(shù)據(jù)的前提下推薦方法一。其它情況下,請使用方法二或者方法三,性能方面和開銷方面都比較優(yōu)秀。
# 擴(kuò)容slice
動態(tài)擴(kuò)容是slice最大的特點。我們?nèi)粘J褂胹lice的過程中,并不會感受到slice的真實容量變化情況,golang底層會幫助我們完成對應(yīng)的slice擴(kuò)容。
以下面的代碼為例:
package?main
import?(
????"fmt"
)
func?main()?{
????nums?:=?make([]int,?0,?2)?//?初始設(shè)置為2
????nums?=?append(nums,?1)
????nums?=?append(nums,?2)
????nums?=?append(nums,?3)?//?觸發(fā)擴(kuò)容
????nums?=?append(nums,?4)
????nums?=?append(nums,?5)
????fmt.Println(nums)
}
讓我們通過go的官方工具來查看匯編結(jié)果:
$?go?tool?compile?-S?main.go
"".main?STEXT?size=490?args=0x0?locals=0x60?funcid=0x0
????0x0000?00000?(main.go:7)????TEXT????"".main(SB),?ABIInternal,?$96-0
????0x0000?00000?(main.go:7)????MOVQ????(TLS),?CX
????0x0009?00009?(main.go:7)????CMPQ????SP,?16(CX)
????0x000d?00013?(main.go:7)????PCDATA??$0,?$-2
????0x000d?00013?(main.go:7)????JLS?480
????0x0013?00019?(main.go:7)????PCDATA??$0,?$-1
????0x0013?00019?(main.go:7)????SUBQ????$96,?SP
????0x0017?00023?(main.go:7)????MOVQ????BP,?88(SP)
????0x001c?00028?(main.go:7)????LEAQ????88(SP),?BP
????0x0021?00033?(main.go:7)????FUNCDATA????$0,?gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
????0x0021?00033?(main.go:7)????FUNCDATA????$1,?gclocals·f207267fbf96a0178e8758c6e3e0ce28(SB)
????0x0021?00033?(main.go:7)????FUNCDATA????$2,?"".main.stkobj(SB)
????0x0021?00033?(main.go:8)????LEAQ????type.int(SB),?AX
????0x0028?00040?(main.go:8)????MOVQ????AX,?(SP)
????0x002c?00044?(main.go:8)????MOVQ????$0,?8(SP)
????0x0035?00053?(main.go:8)????MOVQ????$2,?16(SP)
????0x003e?00062?(main.go:8)????PCDATA??$1,?$0
????0x003e?00062?(main.go:8)????NOP
????0x0040?00064?(main.go:8)????CALL????runtime.makeslice(SB)
????0x0045?00069?(main.go:8)????MOVQ????24(SP),?AX
????0x004a?00074?(main.go:9)????MOVQ????$1,?(AX)
????0x0051?00081?(main.go:10)???MOVQ????$2,?8(AX)
????0x0059?00089?(main.go:11)???LEAQ????type.int(SB),?CX
????0x0060?00096?(main.go:11)???MOVQ????CX,?(SP)
????0x0064?00100?(main.go:11)???MOVQ????AX,?8(SP)
????0x0069?00105?(main.go:11)???MOVQ????$2,?16(SP)
????0x0072?00114?(main.go:11)???MOVQ????$2,?24(SP)
????0x007b?00123?(main.go:11)???MOVQ????$3,?32(SP)
????0x0084?00132?(main.go:11)???CALL????runtime.growslice(SB)?#?調(diào)用runtime的growslice函數(shù)
????0x0089?00137?(main.go:11)???MOVQ????40(SP),?AX
????0x008e?00142?(main.go:11)???MOVQ????56(SP),?CX
????0x0093?00147?(main.go:11)???MOVQ????48(SP),?DX
????0x0098?00152?(main.go:11)???MOVQ????$3,?16(AX)
????0x00a0?00160?(main.go:11)???INCQ????DX
????0x00a3?00163?(main.go:12)???LEAQ????1(DX),?BX
????0x00a7?00167?(main.go:12)???CMPQ????CX,?BX
????0x00aa?00170?(main.go:12)???JCS?403
????0x00b0?00176?(main.go:12)???MOVQ????$4,?(AX)(DX*8)
????0x00b8?00184?(main.go:13)???LEAQ????1(BX),?DX
????0x00bc?00188?(main.go:13)???NOP
????0x00c0?00192?(main.go:13)???CMPQ????CX,?DX
????0x00c3?00195?(main.go:13)???JCS?325
????0x00c9?00201?(main.go:13)???MOVQ????$5,?(AX)(BX*8)
????0x00d1?00209?(main.go:14)???MOVQ????AX,?(SP)
????0x00d5?00213?(main.go:14)???MOVQ????DX,?8(SP)
????0x00da?00218?(main.go:14)???MOVQ????CX,?16(SP)
????0x00df?00223?(main.go:14)???NOP
????0x00e0?00224?(main.go:14)???CALL????runtime.convTslice(SB)
????0x00e5?00229?(main.go:14)???MOVQ????24(SP),?AX
????...?省略不相關(guān)部分
因此,我們進(jìn)一步查看相關(guān)源碼:
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)))
????}
????if?cap?cap?{
????????panic(errorString("growslice:?cap?out?of?range"))
????}
????if?et.size?==?0?{
????????//?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}
????}
????/*
????????擴(kuò)容規(guī)則:
????????1.?長度?1024的情況下,擴(kuò)容2倍
????????2.?長度?>?1024的情況下,擴(kuò)容1.25倍
????*/?
????newcap?:=?old.cap
????doublecap?:=?newcap?+?newcap
????if?cap?>?doublecap?{
????????newcap?=?cap
????}?else?{
????????if?old.len?1024?{
????????????newcap?=?doublecap
????????}?else?{
????????????//?Check?0?
????????????//?and?prevent?an?infinite?loop.
????????????for?0?cap?{
????????????????newcap?+=?newcap?/?4
????????????}
????????????//?Set?newcap?to?the?requested?cap?when
????????????//?the?newcap?calculation?overflowed.
????????????if?newcap?<=?0?{
????????????????newcap?=?cap
????????????}
????????}
????}
????/*
????????內(nèi)存對齊操作,對齊之后的容量相比于上述規(guī)則來說會有所偏差!
????*/
????var?overflow?bool
????var?lenmem,?newlenmem,?capmem?uintptr
????//?Specialize?for?common?values?of?et.size.
????//?For?1?we?don't?need?any?division/multiplication.
????//?For?sys.PtrSize,?compiler?will?optimize?division/multiplication?into?a?shift?by?a?constant.
????//?For?powers?of?2,?use?a?variable?shift.
????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)?<????????newlenmem?=?uintptr(cap)?<????????capmem?=?roundupsize(uintptr(newcap)?<????????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)
????}
????//?The?check?of?overflow?in?addition?to?capmem?>?maxAlloc?is?needed
????//?to?prevent?an?overflow?which?can?be?used?to?trigger?a?segfault
????//?on?32bit?architectures?with?this?example?program:
????//
????//?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"))
????}
????/*
????????分配內(nèi)存塊
????*/
????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)
????????}
????}
????/*
????????把舊slice的內(nèi)容復(fù)制到新slice中
????*/
????memmove(p,?old.array,?lenmem)
????return?slice{p,?old.len,?newcap}
}
小結(jié)一下,通過growslice函數(shù)的底層實現(xiàn),我們能夠知道:
append操作在golang編譯結(jié)果中會轉(zhuǎn)化成runtime.growslice函數(shù),對上層透明,因而開發(fā)者無需關(guān)心slice底層容量大小的問題。
slice觸發(fā)擴(kuò)容操作會創(chuàng)建新的容量數(shù)組,將舊的slice內(nèi)容復(fù)制過去,開發(fā)者需要關(guān)注有多個變量引用同一個slice且發(fā)生擴(kuò)容的情況。
slice的擴(kuò)容原則是:對于長度 < 1024的情況下,2倍增長;對于長度 > 1024的情況下,1.25倍增長。但由于存在內(nèi)存對齊,slice的容量在擴(kuò)容結(jié)束后會有所偏差!
# 總結(jié)
本文詳細(xì)介紹了Go的內(nèi)置類型slice,以常見的代碼為例,通過go官方工具的匯編結(jié)果解析了底層實現(xiàn)。另一方面,本文從源碼角度得出一些開發(fā)者較為容易迷惑的語法問題,例如slice的復(fù)制長度問題、slice擴(kuò)容之后的底層數(shù)組更替問題、slice擴(kuò)容容量大小問題等等。
總的來說,源碼學(xué)習(xí)是一條漫漫長路。筆者認(rèn)為開發(fā)者既不應(yīng)該無視源碼而只靠背誦一些結(jié)論來學(xué)習(xí)語言,也不應(yīng)該過分關(guān)注源碼細(xì)節(jié)導(dǎo)致無從入手。本文從slice的源碼中,也牽扯出一些未能深入解析的概念:Go的runtime、Plan 9匯編、內(nèi)存對齊等,關(guān)于這些概念的進(jìn)一步解析則會隨著筆者的深入學(xué)習(xí)而逐步完善,在此也希望能與讀者共勉!
? ?

???
