<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從源碼的角度去學(xué)習(xí) Go slice

          共 5884字,需瀏覽 12分鐘

           ·

          2021-11-16 02:38

          點擊上方“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個字段:

          1. array:表示當(dāng)前slice存放的數(shù)組元素。

          2. len:表示當(dāng)前slice已經(jīng)使用的長度。

          3. 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),我們能夠知道:

          1. slice的復(fù)制是基于復(fù)制內(nèi)存塊來完成的。

          2. 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.?長度?????????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),我們能夠知道:

          1. append操作在golang編譯結(jié)果中會轉(zhuǎn)化成runtime.growslice函數(shù),對上層透明,因而開發(fā)者無需關(guān)心slice底層容量大小的問題。

          2. slice觸發(fā)擴(kuò)容操作會創(chuàng)建新的容量數(shù)組,將舊的slice內(nèi)容復(fù)制過去,開發(fā)者需要關(guān)注有多個變量引用同一個slice且發(fā)生擴(kuò)容的情況。

          3. 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í)而逐步完善,在此也希望能與讀者共勉!


          ? ?


          喜歡明哥文章的同學(xué)
          歡迎長按下圖訂閱!

          ???

          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本无码色情三级播放免费看电影 | 在线精品播放 | 国产精品三级 | 大香蕉国产精品视频 | 成人在线观看无码 |