Go語言中的slice表示一個具有相同型別元素的可變長序列,語言本身提供了兩個操作方法:
同時slice支援隨機存取。本篇文章主要對slice的具體實現進行總結。
go語言的slice有三個主要的屬性:
因此,在golang官網中的Go Slices: usage and internals對slice的描述如下:
A slice is a descriptor of an array segment. It consists of a pointer to the array, the length of the segment, and its capacity (the maximum length of the segment).
slice是一段array,包括了上面的三個部分,他的物理結構如下:
如果我們通過make([]byte,5,5)
建立了一個len=5,cap=5的slice,其物理結構如此:
如果我們僅僅想使用原陣列的一部分,例如:
s = s[2:4]
則s的物理結構如此:
但實際上,這兩者所參照的是同一塊連續的空間,如果我們修改其中一個,另一個也會跟著修改。實際上,slice在go語言中的程式碼錶示為:
type slice struct {
array unsafe.Pointer
len int
cap int
}
我們是如何知道這件事的呢?請看繼續閱讀該文章。
go語言為slice提供了兩個修改類操作:
接下來我們會對這兩個操作進行分析。
slice的定義(分配空間)有三種方式:
s := []int{1,2,3}
make([]T, len, cap)
s := array[1:2]
還有兩種宣告方式(不分配空間):
var s []int
s := []int{}
接下來我們通過一組範例程式碼,檢視slice的建立流程,以及上面的定義與宣告的區別。
字面量建立方式
// main.go
package main
import "fmt"
func main() {
s1 := []int{1,2,3}
fmt.Println(s1)
}
這組程式碼給出了一個通過字面量方式建立的slice s1
,我們通過delve工具對這部分程式碼進行debug。命令列進入到main.go所在目錄,鍵入如下命令:
dlv debug
# 為main包的main函數第1行即檔案第7行打上斷點
b main.go:7
# 執行到斷點處
c
# 對要執行的部分進行反組合
disassemble
我們就可以看到如下程式碼:
TEXT main.main(SB) D:/code/Notes/docs/go/list/main.go
main.go:6 0x948300 493b6610 cmp rsp, qword ptr [r14+0x10]
main.go:6 0x948304 0f86f5000000 jbe 0x9483ff
main.go:6 0x94830a 4883ec78 sub rsp, 0x78
main.go:6 0x94830e 48896c2470 mov qword ptr [rsp+0x70], rbp
main.go:6 0x948313 488d6c2470 lea rbp, ptr [rsp+0x70]
=> main.go:7 0x948318* 488d05a1940000 lea rax, ptr [rip+0x94a1]
main.go:7 0x94831f 90 nop
# 呼叫runtime的newobject建立一個新的物件
main.go:7 0x948320 e81b53f6ff call $runtime.newobject
# 將呼叫結果(即新slice的地址)存到棧頂中
main.go:7 0x948325 4889442428 mov qword ptr [rsp+0x28], rax
# 把1放入slice中
main.go:7 0x94832a 48c70001000000 mov qword ptr [rax], 0x1
# 從棧頂將slice的地址取出放入rcx暫存器中
main.go:7 0x948331 488b4c2428 mov rcx, qword ptr [rsp+0x28]
main.go:7 0x948336 8401 test byte ptr [rcx], al
# 把2放入slice中
main.go:7 0x948338 48c7410802000000 mov qword ptr [rcx+0x8], 0x2
main.go:7 0x948340 488b4c2428 mov rcx, qword ptr [rsp+0x28]
main.go:7 0x948345 8401 test byte ptr [rcx], al
# 把3放入slice中
main.go:7 0x948347 48c7411003000000 mov qword ptr [rcx+0x10], 0x3
main.go:7 0x94834f 488b4c2428 mov rcx, qword ptr [rsp+0x28]
main.go:7 0x948354 8401 test byte ptr [rcx], al
main.go:7 0x948356 eb00 jmp 0x948358
# 最後設定slice的指標,並將len和cap都設定為3
main.go:7 0x948358 48894c2440 mov qword ptr [rsp+0x40], rcx
main.go:7 0x94835d 48c744244803000000 mov qword ptr [rsp+0x48], 0x3
main.go:7 0x948366 48c744245003000000 mov qword ptr [rsp+0x50], 0x3
由此可見,使用字面量建立slice時,len和cap都會設定為初始化資料的個數。
可以簡單看一下剛才使用的runtime.newobject()
,該函數在runtime/malloc.go檔案
中,程式碼如下:
func newobject(typ *_type) unsafe.Pointer {
return mallocgc(typ.size, typ, true)
}
本質上還是通過記憶體管理機制為一個物件申請一塊連續空間並返回對應指標。
make函數建立:
// main.go
package main
import "fmt"
func main() {
s := make([]int, 10,20)
fmt.Println(s)
}
該例子使用make方式建立了slice s
,其len=10,cap=20,同樣使用delve進行debug,指令碼同上,我們得到的反組合結果如下:
TEXT main.main(SB) D:/code/Notes/docs/go/list/main.go
main.go:6 0xea8300 493b6610 cmp rsp, qword ptr [r14+0x10]
main.go:6 0xea8304 0f86cb000000 jbe 0xea83d5
main.go:6 0xea830a 4883ec70 sub rsp, 0x70
main.go:6 0xea830e 48896c2468 mov qword ptr [rsp+0x68], rbp
main.go:6 0xea8313 488d6c2468 lea rbp, ptr [rsp+0x68]
=> main.go:7 0xea8318* 488d0541840000 lea rax, ptr [rip+0x8441]
main.go:7 0xea831f bb0a000000 mov ebx, 0xa
main.go:7 0xea8324 b914000000 mov ecx, 0x14
# 呼叫runtime.makeslice函數建立slice(其實也只是建立了一個指標)
main.go:7 0xea8329 e8b244faff call $runtime.makeslice
# 為建立好的物件設定起始指標,len和cap
main.go:7 0xea832e 4889442438 mov qword ptr [rsp+0x38], rax
main.go:7 0xea8333 48c74424400a000000 mov qword ptr [rsp+0x40], 0xa
main.go:7 0xea833c 48c744244814000000 mov qword ptr [rsp+0x48], 0x14
可以看到,這裡不再使用runtime.newobject()
建立物件了,而是通過runtime.mallocslice()
方法,該方法在runtime/slice.go檔案
中,原始碼如下:
func makeslice(et *_type, len, cap int) unsafe.Pointer {
// 計算一下要申請的空間大小
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
// 並判斷len和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()
}
// 最後還是要靠剛才的方法申請空間返回指標
return mallocgc(mem, et, true)
}
不過從這裡可以看到,slice底層物理空間的大小不是無限分配的,而是有上限的,其上限就是maxAlloc
,該值的大小依賴於heapAddrBites
,而heapAddrBites
與作業系統有關。
maxAlloc = (1 << heapAddrBits) - (1-_64bit)*1
切取其他資料結構
// main.go
package main
import "fmt"
func main() {
s := [4]int{1,2,3,4}
s3 := s[1:]
fmt.Println(s3)
}
該例子通過陣列s建立了其slice s3
,並且內容為s的第2條和第三條資料,len=2。這裡反組合一下,看一下結果:
main.go:6 0xdc8318 488d05a1940000 lea rax, ptr [rip+0x94a1]
main.go:6 0xdc831f 90 nop
# 為陣列s分配空間
main.go:6 0xdc8320 e81b53f6ff call $runtime.newobject
# 為陣列s填充資料
main.go:6 0xdc8325 4889442428 mov qword ptr [rsp+0x28], rax
main.go:6 0xdc832a 48c70001000000 mov qword ptr [rax], 0x1
main.go:6 0xdc8331 48c7400802000000 mov qword ptr [rax+0x8], 0x2
main.go:6 0xdc8339 48c7401003000000 mov qword ptr [rax+0x10], 0x3
main.go:6 0xdc8341 48c7401804000000 mov qword ptr [rax+0x18], 0x4
=> main.go:7 0xdc8349* 488b4c2428 mov rcx, qword ptr [rsp+0x28]
main.go:7 0xdc834e 8401 test byte ptr [rcx], al
main.go:7 0xdc8350 eb00 jmp 0xdc8352
main.go:7 0xdc8352 eb00 jmp 0xdc8354
main.go:7 0xdc8354 488d5108 lea rdx, ptr [rcx+0x8]
# 為切片設定起始地址以及len和cap
main.go:7 0xdc8358 4889542440 mov qword ptr [rsp+0x40], rdx
main.go:7 0xdc835d 48c744244802000000 mov qword ptr [rsp+0x48], 0x2
main.go:7 0xdc8366 48c744245003000000 mov qword ptr [rsp+0x50], 0x3
這個例子有兩個點要注意:
原始容量減去slice起始值
,這裡需要特別注意兩種宣告方式
// main.go
package main
import "fmt"
func main() {
var s4 []int
s5 := []int{}
fmt.Println(s4)
fmt.Println(s5)
}
該例子實現了對slice的兩種宣告方式,首先檢視第7行於第9行的組合程式碼:
# 將slice的起始地址設定為0
main.go:7 0xe58326 48c744246800000000 mov qword ptr [rsp+0x68], 0x0
main.go:7 0xe5832f 440f117c2470 movups xmmword ptr [rsp+0x70], xmm15
main.go:9 0xe58350 440f117c2440 movups xmmword ptr [rsp+0x40], xmm15
main.go:9 0xe58356 488d542440 lea rdx, ptr [rsp+0x40]
main.go:9 0xe5835b 4889542430 mov qword ptr [rsp+0x30], rdx
# 取出連續的三個空間
main.go:9 0xe58360 488b442468 mov rax, qword ptr [rsp+0x68]
main.go:9 0xe58365 488b5c2470 mov rbx, qword ptr [rsp+0x70]
main.go:9 0xe5836a 488b4c2478 mov rcx, qword ptr [rsp+0x78]
# 將其轉化為slice再進行列印
main.go:9 0xe5836f e80c2af6ff call $runtime.convTslice
main.go:9 0xe58374 4889442428 mov qword ptr [rsp+0x28], rax
main.go:9 0xe58379 488b7c2430 mov rdi, qword ptr [rsp+0x30]
main.go:9 0xe5837e 8407 test byte ptr [rdi], al
main.go:9 0xe58380 488d1599750000 lea rdx, ptr [rip+0x7599]
main.go:9 0xe58387 488917 mov qword ptr [rdi], rdx
main.go:9 0xe5838a 488d5708 lea rdx, ptr [rdi+0x8]
main.go:9 0xe5838e 833dbb610f0000 cmp dword ptr [runtime.writeBarrier], 0x0
main.go:9 0xe58395 7402 jz 0xe58399
main.go:9 0xe58397 eb06 jmp 0xe5839f
main.go:9 0xe58399 48894708 mov qword ptr [rdi+0x8], rax
main.go:9 0xe5839d eb0a jmp 0xe583a9
main.go:9 0xe5839f 4889d7 mov rdi, rdx
main.go:9 0xe583a2 e819ccfbff call $runtime.gcWriteBarrier
main.go:9 0xe583a7 eb00 jmp 0xe583a9
main.go:9 0xe583a9 488b442430 mov rax, qword ptr [rsp+0x30]
main.go:9 0xe583ae 8400 test byte ptr [rax], al
main.go:9 0xe583b0 eb00 jmp 0xe583b2
main.go:9 0xe583b2 4889842498000000 mov qword ptr [rsp+0x98], rax
main.go:9 0xe583ba 48c78424a000000001000000 mov qword ptr [rsp+0xa0], 0x1
main.go:9 0xe583c6 48c78424a800000001000000 mov qword ptr [rsp+0xa8], 0x1
main.go:9 0xe583d2 bb01000000 mov ebx, 0x1
main.go:9 0xe583d7 4889d9 mov rcx, rbx
main.go:9 0xe583da e801abffff call $fmt.Println
對於var s4 []int
此類宣告,go僅僅是給該物件設定了一個nil指標,真正使用的時候,將其通過runtime.convTslice()
轉化為slice,再使用。runtime.convTslice()
原始碼如下:
func convTslice(val []byte) (x unsafe.Pointer) {
// Note: this must work for any element type, not just byte.
// 判斷起始指標是否為nil,是則返回一個空slice
if (*slice)(unsafe.Pointer(&val)).array == nil {
x = unsafe.Pointer(&zeroVal[0])
} else {
// 否則將記憶體中的資料給一個新的地址儲存
x = mallocgc(unsafe.Sizeof(val), sliceType, true)
*(*[]byte)(x) = val
}
return
}
這裡有一個點需要注意,convTslice()方法中入參val
是一個struct slice{}
,由此,我們就可以追溯到slice的資料結構是如下模樣的:
type slice struct {
array unsafe.Pointer
len int
cap int
}
而對於s5 := []int{}
這種宣告方式,其反組合程式碼如下:
# 通過runtime.zerobase 返回一個預設0值
main.go:8 0xe58335 488d15a4610f00 lea rdx, ptr [runtime.zerobase]
main.go:8 0xe5833c 4889542438 mov qword ptr [rsp+0x38], rdx
main.go:8 0xe58341 8402 test byte ptr [rdx], al
main.go:8 0xe58343 eb00 jmp 0xe58345
# 將其寫到s5的位置上
main.go:8 0xe58345 4889542450 mov qword ptr [rsp+0x50], rdx
main.go:8 0xe5834a 440f117c2458 movups xmmword ptr [rsp+0x58], xmm15
使用s5時也是需要通過runtime.convTslice()
將記憶體空間中的資料轉化為一個slice。可以看到這兩種方式都沒有真正的分配一塊記憶體,而是隻寫了一個物件的指標,對於len和cap都沒有進行初始化,否則應該有連續三個8位元組的塊被初始化
。
總結一下,通過上面的分析,我們知道針對於5種建立slice的方式,其內部實現邏輯如下:
s := []int{1,2,3}
,該種方式會調用runtime.newobject
範例化一個cap為提供資料個數的連續記憶體塊用於存放資料,本例中為3,建立的slice物件。
make([]T, len, cap)
,該方式會通過runtime.makeslice
建立一個大小為cap的記憶體塊,然後按照給定的引數將資料寫入slice中:
s := array[1:2]
,該方式不會再申請實體記憶體,而只是建立slice,並修改其值
var s []int
:賦值nil,使用時轉化為slices := []int{}
:賦值為nil,使用時轉化為sliceslice本身支援資料的隨機存取,計算機基礎知識告訴我們,底層是通過計算目標資料的地址直接存取的,這裡我們做實驗驗證一下,檢視如下程式碼:
// main.go
package main
import "fmt"
func main() {
s := []int{1,2,3,4,5}
s[2] = 10
s[10] = 9
fmt.Println(s)
}
上面的例子建立了一個slice s
,並對其第3和第10個元素進行存取,明顯前者是正確存取的,後者會導致程式崩潰,我們通過反組合檢視該過程。
main.go:7 0x418318 488d0561950000 lea rax, ptr [rip+0x9561]
main.go:7 0x41831f 90 nop
# 建立slice底層陣列,併為其填充資料
main.go:7 0x418320 e81b53f6ff call $runtime.newobject
main.go:7 0x418325 4889442428 mov qword ptr [rsp+0x28], rax
main.go:7 0x41832a 48c70001000000 mov qword ptr [rax], 0x1
main.go:7 0x418331 488b4c2428 mov rcx, qword ptr [rsp+0x28]
main.go:7 0x418336 8401 test byte ptr [rcx], al
main.go:7 0x418338 48c7410802000000 mov qword ptr [rcx+0x8], 0x2
main.go:7 0x418340 488b4c2428 mov rcx, qword ptr [rsp+0x28]
main.go:7 0x418345 8401 test byte ptr [rcx], al
main.go:7 0x418347 48c7411003000000 mov qword ptr [rcx+0x10], 0x3
main.go:7 0x41834f 488b4c2428 mov rcx, qword ptr [rsp+0x28]
main.go:7 0x418354 8401 test byte ptr [rcx], al
main.go:7 0x418356 48c7411804000000 mov qword ptr [rcx+0x18], 0x4
main.go:7 0x41835e 488b4c2428 mov rcx, qword ptr [rsp+0x28]
main.go:7 0x418363 8401 test byte ptr [rcx], al
main.go:7 0x418365 48c7412005000000 mov qword ptr [rcx+0x20], 0x5
main.go:7 0x41836d 488b4c2428 mov rcx, qword ptr [rsp+0x28]
main.go:7 0x418372 8401 test byte ptr [rcx], al
main.go:7 0x418374 eb00 jmp 0x418376
# 建立slice物件
main.go:7 0x418376 48894c2440 mov qword ptr [rsp+0x40], rcx
main.go:7 0x41837b 48c744244805000000 mov qword ptr [rsp+0x48], 0x5
main.go:7 0x418384 48c744245005000000 mov qword ptr [rsp+0x50], 0x5
main.go:8 0x41838d eb00 jmp 0x41838f
# 通過計算s[2]的地址存取s[2]的值
main.go:8 0x41838f 48c741100a000000 mov qword ptr [rcx+0x10], 0xa
=> main.go:9 0x418397* 488b4c2448 mov rcx, qword ptr [rsp+0x48]
main.go:9 0x41839c 488b542440 mov rdx, qword ptr [rsp+0x40]
# 使用索引與s的len比較
main.go:9 0x4183a1 4883f90a cmp rcx, 0xa
# 如果沒有問題,則將資料放入slice
main.go:9 0x4183a5 7705 jnbe 0x4183ac
# 索引越界,程式處理出錯,跳轉到錯誤處理
main.go:9 0x4183a7 e998000000 jmp 0x418444
main.go:9 0x4183ac 48c7425009000000 mov qword ptr [rdx+0x50], 0x9
通過這部分反組合程式碼,我們就可以清楚地看到隨機存取的整個過程。
slice本身是可以進行修改的,go提供了append([]T, T...)
方法用於歲slice進行資料追加,同時也通過該方法實現了slice的擴容,接下來我們通過下面的例子對slice的追加策略進行探究。
// main.go
package main
import "fmt"
func main() {
s := []int{}
s = append(s, 1)
s = append(s, 2)
s = append(s, 3)
fmt.Println(s)
}
這裡為了通用性,我們分析第9行程式碼s = append(s, 2)
,因為第8行s = append(s, 1)
必定需要擴容,所以不能代表全部情況,現在檢視第9行反組合程式碼:
main.go:9 0xa58378 488d7302 lea rsi, ptr [rbx+0x2]
main.go:9 0xa5837c 0f1f4000 nop dword ptr [rax], eax
# 比較slice當前cap和需要的容量
main.go:9 0xa58380 4839f1 cmp rcx, rsi
# 如果當前容量夠用,直接插入資料
main.go:9 0xa58383 7302 jnb 0xa58387
# 如果當前容量不夠用,進行擴容
main.go:9 0xa58385 eb02 jmp 0xa58389
main.go:9 0xa58387 eb27 jmp 0xa583b0
# 擴容程式碼
main.go:8 0xa58389 48895c2440 mov qword ptr [rsp+0x40], rbx
main.go:9 0xa5838e 4889c3 mov rbx, rax
main.go:9 0xa58391 4889cf mov rdi, rcx
main.go:9 0xa58394 488d05c5830000 lea rax, ptr [rip+0x83c5]
main.go:9 0xa5839b 4889d1 mov rcx, rdx
main.go:9 0xa5839e 6690 data16 nop
main.go:9 0xa583a0 e87b45faff call $runtime.growslice
main.go:9 0xa583a5 488d7301 lea rsi, ptr [rbx+0x1]
main.go:9 0xa583a9 488b5c2440 mov rbx, qword ptr [rsp+0x40]
main.go:9 0xa583ae eb00 jmp 0xa583b0
# 插入新資料程式碼
main.go:9 0xa583b0 488d14d8 lea rdx, ptr [rax+rbx*8]
main.go:9 0xa583b4 488d5208 lea rdx, ptr [rdx+0x8]
# s = append(s,T),將新的slice放回到原地址中
main.go:9 0xa583b8 48c70202000000 mov qword ptr [rdx], 0x2
main.go:9 0xa583bf 4889442470 mov qword ptr [rsp+0x70], rax
main.go:9 0xa583c4 4889742478 mov qword ptr [rsp+0x78], rsi
main.go:9 0xa583c9 48898c2480000000 mov qword ptr [rsp+0x80], rcx
可以看到,go語言中slice是否需要擴容的判斷並不是在go中實現的,而擴容的具體邏輯是runtime.growslice()
函數。下面檢視runtime.growslice()
原始碼:
// runtime/slice.go
// growslice handles slice growth during append.
// It is passed the slice element type, the old slice, and the desired new minimum capacity,
// and it returns a new slice with at least that capacity, with the old data
// copied into it.
// The new slice's length is set to the old slice's length,
// NOT to the new requested capacity.
// This is for codegen convenience. The old slice's length is used immediately
// to calculate where to write new values during an append.
// TODO: When the old backend is gone, reconsider this decision.
// The SSA backend might prefer the new length or to return only ptr/cap and save stack space.
func growslice(et *_type, old slice, cap int) slice {
...
if cap < old.cap {
panic(errorString("growslice: cap out of range"))
}
...
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
// 1. 計算新slice的容量
// (1) 如果原容量小於256,則是原容量的2倍
const threshold = 256
if old.cap < threshold {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
// (2)否則每次增加 old.cap/4 + 192
// Transition from growing 2x for small slices
// to growing 1.25x for large slices. This formula
// gives a smooth-ish transition between the two.
newcap += (newcap + 3*threshold) / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
var overflow bool
var lenmem, newlenmem, capmem uintptr
// 2. 根據新容量快速算出是否需要多少記憶體
// Specialize for common values of et.size.
// For 1 we don't need any division/multiplication.
// For goarch.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 == goarch.PtrSize:
lenmem = uintptr(old.len) * goarch.PtrSize
newlenmem = uintptr(cap) * goarch.PtrSize
capmem = roundupsize(uintptr(newcap) * goarch.PtrSize)
overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize
newcap = int(capmem / goarch.PtrSize)
case isPowerOfTwo(et.size):
var shift uintptr
if goarch.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)
}
// 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"))
}
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 {
// 3. 分配新slice的物理空間
// 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)
}
}
// 4. 將舊資料拷貝到新空間中
memmove(p, old.array, lenmem)
// 5. 返回生成的slice
return slice{p, old.len, newcap}
}
通過上面的分析,我們可以看到,slice的擴容策略是:
而且注意,這裡產生了一個新的指標,新指標與舊指標指向的位置不同,因此才需要s = append(s, T)。
至此,slice的內容基本分析完畢。