一、內存分配
無論 []A 還是 []*A,切片本身的元數據(切片頭)結構是完全相同的。切片頭是一個包含 3 個字段的結構體:
- ptr:指向底層數組的指針(
8字節)。 - len:切片長度(
8字節)。 - cap:切片容量(
8字節)。
因此,切片頭本身佔用的內存大小 24 字節,內存完全一致。
切片的核心是底層數組,內存分配的核心差異源於底層數組的元素類型:[]A 存儲完整結構體實例,[]*A 存儲指向結構體實例的指針。這直接影響內存佈局、分配方式和總佔用。
1. 底層數組的內存分配
[]A的底層數組每個元素是完整的結構體 A 實例。例如,若 A 包含字段 x int32 和 y string(假設 string 佔 16 字節),則每個 A 實例佔 20 字節(需按最大字段對齊,實際可能因對齊規則調整)。[]*A底層數組每個元素是指向結構體A實例的指針*A。因此,底層數組僅存儲指針,結構體實例可能分散在堆中(除非被編譯器優化到棧上),內存不連續,可能存在碎片。
2.底層數組的內存佔用
[]A底層數組的總內存 = 元素數量 ×sizeOf(A)。[]*A底層數組的總內存 = 元素數量 × 指針大小(8字節)。
3.內存緊湊性
[]A的底層數組是連續存儲的完整結構體實例,內存佈局緊湊,無額外指針開銷。[]*A底層數組僅存儲指針,結構體實例可能分散在堆中(除非被編譯器優化到棧上),內存不連續,可能存在碎片。
4.訪問效率
[]A訪問元素時,直接通過切片頭的ptr + (i × sizeOf(A))計算地址,無需解引用,效率更高(尤其當sizeOf(A)較小時)。[]*A訪問元素時,需先通過ptr + (i × 8)找到指針,再通過指針跳轉到實際結構體地址(解引用),多一次內存訪問,效率略低(但差異通常可忽略,除非高頻操作)。
5.複製與傳遞
- 複製
[]A或[]*A切片時,僅複製切片頭(24字節),底層數組不會被複制(共享底層數組)。 - 若傳遞大結構體的
[]A,雖然切片頭複製成本低,但遍歷或操作時需處理大結構體;而[]*A傳遞的是小指針,更適合大結構體或需要頻繁傳遞切片的場景。
二、生命週期
Go 的垃圾回收(GC)基於可達性分析:對象(如切片、結構體實例)若能被“根對象”(全局變量、當前調用棧中的變量等)通過引用鏈訪問到,則存活;否則被回收。
[]A 和 []*A 的生命週期差異核心在於切片與結構體實例的綁定關係。
1. []A 的生命週期:與結構體實例強綁定
底層數組直接存儲結構體實例
- 切片的存活強制底層數組存活:只要切片本身可達(被根對象引用),其底層數組必然可達(切片頭的 ptr 字段指向底層數組)。
- 結構體實例的生命週期由切片決定:底層數組中的每個結構體實例無法被單獨回收。即使實例已無其他用途,只要切片存活,底層數組和所有實例必須保留,直到切片本身不可達(被 GC 回收)。
2. []*A 的生命週期:與結構體實例弱綁定
底層數組僅存儲指針,結構體實例是獨立的對象,底層數組僅僅是結構體實例的一個引用(也可能是唯一的引用):
- 若切片存活(可達),則其元素實例會被切片中的指針間接引用,不會被
GC回收(即使沒有其他變量引用)。 - 若切片本身不可達(如被置為
nil或離開作用域),則其底層數組和指針元素均不可達。此時元素實例若無其他引用鏈,會被GC回收。 - 實例的生命週期獨立於切片:實例可被多個切片或變量共享(通過複製指針)。只要任意一個引用鏈存在(如另一個變量
b := arr[0]),實例就存活;即使切片被回收,只要其他引用存在,實例仍存活。
注:在 64 位系統中,每個指針佔 8 字節,32 位系統佔 4 字節,本文舉例默認 64 位系統。