博客 / 詳情

返回

[]A 與 []*A 的內存分配、生命週期-Golang 🔥

一、內存分配

無論 []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 位系統。

user avatar astraw99 頭像 fgaoxing 頭像 hunterxiong 頭像
3 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.