您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“Golang中Slice使用源碼分析”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Golang中Slice使用源碼分析”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
首先我們來看一段代碼
package main import ( "fmt" "unsafe" ) func main() { var a int var b int8 var c int16 var d int32 var e int64 slice := make([]int, 0) slice = append(slice, 1) fmt.Printf("int:%d\nint8:%d\nint16:%d\nint32:%d\nint64:%d\n", unsafe.Sizeof(a), unsafe.Sizeof(b), unsafe.Sizeof(c), unsafe.Sizeof(d), unsafe.Sizeof(e)) fmt.Printf("slice:%d", unsafe.Sizeof(slice)) }
該程序輸出golang中常用數據類型占多少byte,輸出結果是
int:8
int8:1
int16:2
int32:4
int64:8
slice:24
我們可以看到slice占24byte,為什么會占24byte,這就跟slice底層定義的結構有關,我們在golang的runtime/slice.go中可以找到slice的結構定義,如下:
type slice struct { array unsafe.Pointer//指向底層數組的指針 len int//切片的長度 cap int//切片的容量 }
我們可以看到slice中定義了三個變量,一個是指向底層數字的指針array,另外兩個是切片的長度len和切片的容量cap。
簡單了解了slice的底層結構后,我們來看下slice的初始化,在golang中slice有多重初始化方式,在這里我們就不一一介紹了,感興趣的朋友可以自行百度,我們主要關注slice在底層是如何初始化的,首先我們來看一段代碼
package main import "fmt" func main() { slice := make([]int, 0) slice = append(slice, 1) fmt.Println(slice, len(slice), cap(slice)) }
很簡單的一段代碼,make一個slice,往slice中append一個一個1,打印slice內容,長度和容量,接下來我們利用gotool提供的工具將以上代碼反匯編
go tool compile -S slice.go
得到匯編代碼如下(截取部分):
0x0000 00000 (slice.go:8) TEXT "".main(SB), ABIInternal, $152-0 0x0000 00000 (slice.go:8) MOVQ (TLS), CX 0x0009 00009 (slice.go:8) LEAQ -24(SP), AX 0x000e 00014 (slice.go:8) CMPQ AX, 16(CX) 0x0012 00018 (slice.go:8) JLS 375 0x0018 00024 (slice.go:8) SUBQ $152, SP 0x001f 00031 (slice.go:8) MOVQ BP, 144(SP) 0x0027 00039 (slice.go:8) LEAQ 144(SP), BP 0x002f 00047 (slice.go:8) FUNCDATA $0, gclocals- f14a5bc6d08bc46424827f54d2e3f8ed(SB)//編譯器產生,用于保存一些垃圾收集相關的信息 0x002f 00047 (slice.go:8) FUNCDATA $1, gclocals- 3e7bd269c75edba02eda3b9069a96409(SB) 0x002f 00047 (slice.go:8) FUNCDATA $2, gclocals- f6aec3988379d2bd21c69c093370a150(SB) 0x002f 00047 (slice.go:8) FUNCDATA $3, "".main.stkobj(SB) 0x002f 00047 (slice.go:9) PCDATA $0, $1 0x002f 00047 (slice.go:9) PCDATA $1, $0 0x002f 00047 (slice.go:9) LEAQ type.int(SB), AX 0x0036 00054 (slice.go:9) PCDATA $0, $0 0x0036 00054 (slice.go:9) MOVQ AX, (SP) 0x003a 00058 (slice.go:9) XORPS X0, X0 0x003d 00061 (slice.go:9) MOVUPS X0, 8(SP) 0x0042 00066 (slice.go:9) CALL runtime.makeslice(SB)//初始化slice 0x0047 00071 (slice.go:9) PCDATA $0, $1 0x0047 00071 (slice.go:9) MOVQ 24(SP), AX 0x004c 00076 (slice.go:10) PCDATA $0, $2 0x004c 00076 (slice.go:10) LEAQ type.int(SB), CX 0x0053 00083 (slice.go:10) PCDATA $0, $1 0x0053 00083 (slice.go:10) MOVQ CX, (SP) 0x0057 00087 (slice.go:10) PCDATA $0, $0 0x0057 00087 (slice.go:10) MOVQ AX, 8(SP) 0x005c 00092 (slice.go:10) XORPS X0, X0 0x005f 00095 (slice.go:10) MOVUPS X0, 16(SP) 0x0064 00100 (slice.go:10) MOVQ $1, 32(SP) 0x006d 00109 (slice.go:10) CALL runtime.growslice(SB)//append操作 0x0072 00114 (slice.go:10) PCDATA $0, $1 0x0072 00114 (slice.go:10) MOVQ 40(SP), AX 0x0077 00119 (slice.go:10) MOVQ 48(SP), CX 0x007c 00124 (slice.go:10) MOVQ 56(SP), DX 0x0081 00129 (slice.go:10) MOVQ DX, "".slice.cap+72(SP) 0x0086 00134 (slice.go:10) MOVQ $1, (AX) 0x008d 00141 (slice.go:11) PCDATA $0, $0 0x008d 00141 (slice.go:11) MOVQ AX, (SP) 0x0091 00145 (slice.go:10) LEAQ 1(CX), AX 0x0095 00149 (slice.go:10) MOVQ AX, "".slice.len+64(SP) 0x009a 00154 (slice.go:11) MOVQ AX, 8(SP) 0x009f 00159 (slice.go:11) MOVQ DX, 16(SP) 0x00a4 00164 (slice.go:11) CALL runtime.convTslice(SB)//類型轉換 0x00a9 00169 (slice.go:11) PCDATA $0, $1 0x00a9 00169 (slice.go:11) MOVQ 24(SP), AX 0x00ae 00174 (slice.go:11) PCDATA $0, $0 0x00ae 00174 (slice.go:11) PCDATA $1, $1 0x00ae 00174 (slice.go:11) MOVQ AX, ""..autotmp_33+88(SP) 0x00b3 00179 (slice.go:11) MOVQ "".slice.len+64(SP), CX 0x00b8 00184 (slice.go:11) MOVQ CX, (SP) 0x00bc 00188 (slice.go:11) CALL runtime.convT64(SB) 0x00c1 00193 (slice.go:11) PCDATA $0, $1 0x00c1 00193 (slice.go:11) MOVQ 8(SP), AX 0x00c6 00198 (slice.go:11) PCDATA $0, $0 0x00c6 00198 (slice.go:11) PCDATA $1, $2 0x00c6 00198 (slice.go:11) MOVQ AX, ""..autotmp_34+80(SP) 0x00cb 00203 (slice.go:11) MOVQ "".slice.cap+72(SP), CX 0x00d0 00208 (slice.go:11) MOVQ CX, (SP) 0x00d4 00212 (slice.go:11) CALL runtime.convT64(SB) 0x00d9 00217 (slice.go:11) PCDATA $0, $1 0x00d9 00217 (slice.go:11) MOVQ 8(SP), AX 0x00de 00222 (slice.go:11) PCDATA $1, $3 0x00de 00222 (slice.go:11) XORPS X0, X0
大家可能看到這里有點蒙,這是在干啥,其實我們只需要關注一些關鍵的信息就好了,主要是這幾行
0x0042 00066 (slice.go:9) CALL runtime.makeslice(SB)//初始化slice 0x006d 00109 (slice.go:10) CALL runtime.growslice(SB)//append操作 0x00a4 00164 (slice.go:11) CALL runtime.convTslice(SB)//類型轉換 0x00bc 00188 (slice.go:11) CALL runtime.convT64(SB) 0x00d4 00212 (slice.go:11) CALL runtime.convT64(SB)
我們能觀察出,底層是調用runtime中的makeslice方法來創建slice的,我們來看一下makeslice函數到底做了什么
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 { // 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() } // Allocate an object of size bytes. // Small objects are allocated from the per-P cache's free lists. // Large objects (> 32 kB) are allocated straight from the heap. return mallocgc(mem, et, true) } func panicmakeslicelen() { panic(errorString("makeslice: len out of range")) } func panicmakeslicecap() { panic(errorString("makeslice: cap out of range")) }
MulUintptr函數源碼
package math import "runtime/internal/sys" const MaxUintptr = ^uintptr(0) // 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 {//a|b < 1<<(4*8) return a * b, false } overflow := b > MaxUintptr/a return a * b, overflow }
簡單來說,makeslice函數的工作主要就是計算slice所需內存大小,然后調用mallocgc進行內存的分配。計算slice所需內存又是通過MulUintptr來實現的,MulUintptr的源碼我們也已經貼出,主要就是用切片中元素大小和切片的容量相乘計算出所需占用的內存空間,如果內存溢出,或者計算出的內存大小大于最大可分配內存,MulUintptr的overflow會返回true,makeslice就會報錯。另外如果傳入長度小于0或者長度小于容量,makeslice也會報錯。
首先我們來看一段程序
package main import ( "fmt" "unsafe" ) func main() { slice := make([]int, 0, 10) slice = append(slice, 1) fmt.Println(unsafe.Pointer(&slice[0]), len(slice), cap(slice)) slice = append(slice, 2) fmt.Println(unsafe.Pointer(&slice[0]), len(slice), cap(slice)) }
我們直接給出結果
0xc00009e000 1 10
0xc00009e000 2 10
我們可以看到,當slice容量足夠時,我們往slice中append一個2,slice底層數組指向的內存地址沒有發生改變;再看一段程序
func main() { slice := make([]int, 0) slice = append(slice, 1) fmt.Printf("%p %d %d\n", unsafe.Pointer(&slice[0]), len(slice), cap(slice)) slice = append(slice, 2) fmt.Printf("%p %d %d\n", unsafe.Pointer(&slice[0]), len(slice), cap(slice)) }
輸出結果是
0xc00009a008 1 1
0xc00009a030 2 2
我們可以看到當往slice中append一個1后,slice底層數組的指針指向地址0xc00009a008,長度為1,容量為1。這時再往slice中append一個2,那么slice的容量不夠了,此時底層數組會發生copy,會重新分配一塊新的內存地址,容量也變成了2,所以我們會看到底層數組的指針指向地址發生了改變。根據之前匯編的結果我們知曉了,append操作其實是調用了runtime/slice.go中的growslice函數,我們來看下源碼:
func growslice(et *_type, old slice, cap int) slice { ... ... if cap < old.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} } newcap := old.cap//1280 doublecap := newcap + newcap//1280+1280=2560 if cap > doublecap { newcap = cap } else { if old.len < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { newcap += newcap / 4//1280*1.25=1600 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } } } ... }
我們主要關注下cap的擴容規則,從源碼中我們可以簡單的總結出slice容量的擴容規則:當原slice的cap小于1024時,新slice的cap變為原來的2倍;原slice的cap大于1024時,新slice變為原來的1.25倍,我們寫個程序來驗證下:
package main import "fmt" func main() { slice := make([]int, 0) oldCap := cap(slice) for i := 0; i < 4096; i++ { slice = append(slice, i) newCap := cap(slice) if newCap != oldCap { fmt.Printf("oldCap = %-4d after append %-4d newCap = %-4d\n", oldCap, i, newCap) oldCap = newCap } } }
這段程序實現的功能是:當cap發生改變時,打印出cap改變前后的值。我們來看程序的輸出結果:
oldCap = 0 after append 0 newCap = 1
oldCap = 1 after append 1 newCap = 2
oldCap = 2 after append 2 newCap = 4
oldCap = 4 after append 4 newCap = 8
oldCap = 8 after append 8 newCap = 16
oldCap = 16 after append 16 newCap = 32
oldCap = 32 after append 32 newCap = 64
oldCap = 64 after append 64 newCap = 128
oldCap = 128 after append 128 newCap = 256
oldCap = 256 after append 256 newCap = 512
oldCap = 512 after append 512 newCap = 1024
oldCap = 1024 after append 1024 newCap = 1280
oldCap = 1280 after append 1280 newCap = 1696
oldCap = 1696 after append 1696 newCap = 2304
oldCap = 2304 after append 2304 newCap = 3072
oldCap = 3072 after append 3072 newCap = 4096
一開始的時候看起來跟我說的擴容規則是一樣的,從1->2->4->8->16…->1024,都是成倍增長,當cap大于1024后,再append元素,cap變為1280,變成了1024的1.25倍,也符合我們的規則;但是繼續append,1280->1696,似乎不是1.25倍,而是1.325倍,可見擴容規則并不是我們以上所說的那么簡單,我們再繼續往下看源碼:
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)//13568 overflow = uintptr(newcap) > maxAlloc/sys.PtrSize newcap = int(capmem / sys.PtrSize)//13568/8=1696 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) }
我們看到每個case中都執行了roundupsize,我們再看下roundupsize的源碼,如下:
package runtime // Returns size of the memory block that mallocgc will allocate if you ask for the size. func roundupsize(size uintptr) uintptr { if size < _MaxSmallSize {//size=1600*8=12800<32768 if size <= smallSizeMax-8 {//12800<=0 return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]]) } else { return uintptr(class_to_size[size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]])//size_to_class128[92]= 56 //class_to_size[56]=13568 //13568/8=1696 } } if size+_PageSize < size { return size } return round(size, _PageSize) } const _MaxSmallSize = 32768 const smallSizeDiv = 8 const smallSizeMax = 1024 const largeSizeDiv = 128
其實roundupsize是內存對齊的過程,我們知道golang中內存分配是根據對象大小來配不同的mspan,為了避免造成過多的內存碎片,slice在擴容中需要對擴容后的cap容量進行內存對齊的操作,接下來我們對照源碼來實際計算下cap容量是否由1280變成了1696
從以上流程圖可以看出,cap在變成1600后又進入了內存對齊的過程,最終cap變為了1696。
go中的slice是支持截取操作的,雖然使用起來非常的方便,但是有很多坑,稍有不慎就會出現bug且不易排查。讓我們來看一段程序
package main import "fmt" func main() { slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := slice[2:5] s2 := s1[2:7] fmt.Printf("len=%-4d cap=%-4d slice=%-1v \n", len(slice), cap(slice), slice) fmt.Printf("len=%-4d cap=%-4d s1=%-1v \n", len(s1), cap(s1), s1) fmt.Printf("len=%-4d cap=%-4d s2=%-1v \n", len(s2), cap(s2), s2) }
程序輸出
len=10 cap=10 slice=[0 1 2 3 4 5 6 7 8 9]
len=3 cap=8 s1=[2 3 4]
len=5 cap=6 s2=[4 5 6 7 8]
s1的長度變成3,cap變為8(默認截取到最大容量), 但是s2截取s1的第2到第7個元素,左閉右開,很多人想問,s1根本沒有那么元素啊,但是實際情況是s2截取到了,并且沒有發生數組越界,原因就是s2實際截取的是底層數組,目前slice、s1、s2都是共用的同一個底層數組。我們繼續操作
fmt.Println("--------append 100----------------") s2 = append(s2, 100)
輸出結果是:
--------append 100----------------
len=10 cap=10 slice=[0 1 2 3 4 5 6 7 8 100]
len=3 cap=8 s1=[2 3 4]
len=6 cap=6 s2=[4 5 6 7 8 100]
我們看到往s2里append數據影響到了slice,正是因為兩者底層數組是一樣的;但是既然都是共用的同一底層數組,s1為什么沒有100,這個問題再下一節會講到,大家稍安勿躁。我們繼續進行操作:
fmt.Println("--------append 200----------------") s2 = append(s2, 200)
輸出結果是:
--------append 200----------------
len=10 cap=10 slice=[0 1 2 3 4 5 6 7 8 100]
len=3 cap=8 s1=[2 3 4]
len=7 cap=12 s2=[4 5 6 7 8 100 200]
我們看到繼續往s2中append一個200,但是只有s2發生了變化,slice并未改變,為什么呢?對,是因為在append完100后,s2的容量已滿,再往s2中append,底層數組發生復制,系統分配了一塊新的內存地址給s2,s2的容量也翻倍了。我們繼續操作:
fmt.Println("--------modify s1----------------") s1[2] = 20
輸出會是什么樣呢?
--------modify s1----------------
len=10 cap=10 slice=[0 1 2 3 20 5 6 7 8 100]
len=3 cap=8 s1=[2 3 20]
len=7 cap=12 s2=[4 5 6 7 8 100 200]
這就很容易理解了,我們對s1進行更新,影響了slice,因為兩者共用的還是同一底層數組,s2未發生改變是因為在上一步時底層數組已經發生了變化;
以此來看,slice截取的坑確實很多,極容易出現bug,并且難以排查,大家在使用的時候一定注意。
上一節中對slice進行的截取,新的slice和原始slice共用同一個底層數組,因此可以看做是對slice的淺拷貝,那么在go中如何實現對slice的深拷貝呢?那么就要依賴golang提供的copy函數了,我們用一段程序來簡單看下如何實現深拷貝:
func main() { // Creating slices slice1 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} var slice2 []int slice3 := make([]int, 5) // Before copying fmt.Println("------------before copy-------------") fmt.Printf("len=%-4d cap=%-4d slice1=%v\n", len(slice1), cap(slice1), slice1) fmt.Printf("len=%-4d cap=%-4d slice2=%v\n", len(slice2), cap(slice2), slice2) fmt.Printf("len=%-4d cap=%-4d slice3=%v\n", len(slice3), cap(slice3), slice3) // Copying the slices copy_1 := copy(slice2, slice1) fmt.Println() fmt.Printf("len=%-4d cap=%-4d slice1=%v\n", len(slice1), cap(slice1), slice1) fmt.Printf("len=%-4d cap=%-4d slice2=%v\n", len(slice2), cap(slice2), slice2) fmt.Println("Total number of elements copied:", copy_1) }
首先定義了三個slice,然后將slice1 copy到slice2,我們來看下輸出結果:
------------before copy-------------
len=10 cap=10 slice1=[0 1 2 3 4 5 6 7 8 9]
len=0 cap=0 slice2=[]
len=5 cap=5 slice3=[0 0 0 0 0]
len=10 cap=10 slice1=[0 1 2 3 4 5 6 7 8 9]
len=0 cap=0 slice2=[]
Total number of elements copied: 0
我們發現slice1的內容并未copy到slice2,為什么呢?我們再試下將slice1 copy到slice3,如下:
copy_2 := copy(slice3, slice1)
輸出結果:
len=10 cap=10 slice1=[0 1 2 3 4 5 6 7 8 9]
len=5 cap=5 slice3=[0 1 2 3 4]
Total number of elements copied: 5
我們看到copy成功,slice3和slice2唯一的區別就是slice3的容量為5,而slice2容量為0,那么是否是深拷貝呢,我們修改slice3的內容看下:
slice3[0] = 100
我們再看下輸出結果:
len=10 cap=10 slice1=[0 1 2 3 4 5 6 7 8 9]
len=5 cap=5 slice3=[100 1 2 3 4]
我們可以看到修改slice3后,slice1的值并未改變,可見copy實現的是深拷貝。由此可見,copy函數為slice提供了深拷貝能力,但是需要在拷貝前申請內存空間。參照makeslice和growslice我們對本節一開始的程序進行反匯編,得到匯編代碼(部分)如下:
0x0080 00128 (slice.go:10) CALL runtime.makeslice(SB) 0x0085 00133 (slice.go:10) PCDATA $0, $1 0x0085 00133 (slice.go:10) MOVQ 24(SP), AX 0x008a 00138 (slice.go:10) PCDATA $1, $2 0x008a 00138 (slice.go:10) MOVQ AX, ""..autotmp_75+96(SP) 0x008f 00143 (slice.go:11) PCDATA $0, $4 0x008f 00143 (slice.go:11) MOVQ ""..autotmp_74+104(SP), CX 0x0094 00148 (slice.go:11) CMPQ AX, CX 0x0097 00151 (slice.go:11) JEQ 176 0x0099 00153 (slice.go:11) PCDATA $0, $5 0x0099 00153 (slice.go:11) MOVQ AX, (SP) 0x009d 00157 (slice.go:11) PCDATA $0, $0 0x009d 00157 (slice.go:11) MOVQ CX, 8(SP) 0x00a2 00162 (slice.go:11) MOVQ $40, 16(SP) 0x00ab 00171 (slice.go:11) CALL runtime.memmove(SB) 0x00b0 00176 (slice.go:12) MOVQ $10, (SP) 0x00b8 00184 (slice.go:12) CALL runtime.convT64(SB)
我們發現copy函數其實是調用runtime.memmove,其實我們在研究runtime/slice.go文件中的源碼的時候,會發現有一個slicecopy函數,這個函數最終就是調用runtime.memmove來實現slice的copy的,我們看下源碼:
func slicecopy(to, fm slice, width uintptr) int { // 如果源切片或者目標切片有一個長度為0,那么就不需要拷貝,直接 return if fm.len == 0 || to.len == 0 { return 0 } // n 記錄下源切片或者目標切片較短的那一個的長度 n := fm.len if to.len < n { n = to.len } // 如果入參 width = 0,也不需要拷貝了,返回較短的切片的長度 if width == 0 { return n } //如果開啟競爭檢測 if raceenabled { callerpc := getcallerpc() pc := funcPC(slicecopy) racewriterangepc(to.array, uintptr(n*int(width)), callerpc, pc) racereadrangepc(fm.array, uintptr(n*int(width)), callerpc, pc) } if msanenabled { msanwrite(to.array, uintptr(n*int(width))) msanread(fm.array, uintptr(n*int(width))) } size := uintptr(n) * width if size == 1 { // common case worth about 2x to do here // TODO: is this still worth it with new memmove impl? //如果只有一個元素,那么直接進行地址轉換 *(*byte)(to.array) = *(*byte)(fm.array) // known to be a byte pointer } else { //如果不止一個元素,那么就從 fm.array 地址開始,拷貝到 to.array 地址之后,拷貝個數為size memmove(to.array, fm.array, size) } return n }
源碼解讀見中文注釋。
slice在作為函數參數進行傳遞的時候,是值傳遞還是引用傳遞,我們來看一段程序:
package main import "fmt" func main() { slice := make([]int, 0, 10) slice = append(slice, 1) fmt.Println(slice, len(slice), cap(slice)) fn(slice) fmt.Println(slice, len(slice), cap(slice)) } func fn(in []int) { in = append(in, 5) }
很簡單的一段程序,我們直接來看輸出結果
[1] 1 10
[1] 1 10
可見fn內的append操作并未對slice產生影響,那我們再看一段代碼:
package main import "fmt" func main() { slice := make([]int, 0, 10) slice = append(slice, 1) fmt.Println(slice, len(slice), cap(slice)) fn(slice) fmt.Println(slice, len(slice), cap(slice)) } func fn(in []int) { in[0] = 100 }
輸出是什么?我們來看下
[1] 1 10
[100] 1 10
slice居然改變了,是不是有點混亂?前面我們說到slice底層其實是一個結構體,len、cap、array分別表示長度、容量、底層數組的地址,當slice作為函數的參數傳遞的時候,跟普通結構體的傳遞是沒有區別的;如果直接傳slice,實參slice是不會被函數中的操作改變的,但是如果傳遞的是slice的指針,是會改變原來的slice的;另外,無論是傳遞slice還是slice的指針,如果改變了slice的底層數組,那么都是會影響slice的,這種通過數組下標的方式更新slice數據,是會對底層數組進行改變的,所以就會影響slice。
那么,講到這里,在第一段程序中在fn函數內append的5到哪里去了,不可能憑空消失啊,我們再來看一段程序
package main import "fmt" func main() { slice := make([]int, 0, 10) slice = append(slice, 1) fmt.Println(slice, len(slice), cap(slice)) fn(slice) fmt.Println(slice, len(slice), cap(slice)) s1 := slice[0:9]//數組截取 fmt.Println(s1, len(s1), cap(s1)) } func fn(in []int) { in = append(in, 5) }
我們來看輸出結果
[1] 1 10
[1] 1 10
[1 5 0 0 0 0 0 0 0] 9 10
顯然,雖然在append后,slice中并未展示出5,也無法通過slice[1]取到(會數組越界),但是實際上底層數組已經有了5這個元素,但是由于slice的len未發生改變,所以我們在上層是無法獲取到5這個元素的。那么,再問一個問題,我們是不是可以手動強制改變slice的len長度,讓我們可以獲取到5這個元素呢?是可以的,我們來看一段程序
package main import ( "fmt" "reflect" "unsafe" ) func main() { slice := make([]int, 0, 10) slice = append(slice, 1) fmt.Println(slice, len(slice), cap(slice)) fn(slice) fmt.Println(slice, len(slice), cap(slice)) (*reflect.SliceHeader)(unsafe.Pointer(&slice)).Len = 2 //強制修改slice長度 fmt.Println(slice, len(slice), cap(slice)) } func fn(in []int) { in = append(in, 5) }
我們來看輸出結果
[1] 1 10
[1] 1 10
[1 5] 2 10
可以看出,通過強制修改slice的len,我們可以獲取到了5這個元素。
所以再次回答一開始我們提出的問題,slice是值傳遞還是引用傳遞?答案是值傳遞!
以上,在使用golang中的slice的時候大家一定注意,否則稍有不慎就會出現bug。
讀到這里,這篇“Golang中Slice使用源碼分析”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。