如果 dispatch 篇檢視了「呼叫如何被轉譯」,這次要看的是「值如何被配置」。
這是上一篇文章 Swift 深入解析 #5 的延續。
struct 的大小是屬性大小的總和嗎?不是。
如果改變屬性宣告順序就可能改變 struct 大小,你相信嗎?這是深入解析系列的記憶體配置篇。
設計要放入陣列的數十萬個模型時,或 Instruments 的記憶體圖比預期更肥大時,這些知識就很重要。
也能回答 some·any 篇留下的問題:「那個盒子實際上有多大?」
三種尺寸 — size、stride、alignment
Swift 標準提供查詢型別記憶體尺寸的工具。它就是 MemoryLayout。
MemoryLayout<Int>.size // 8
MemoryLayout<Bool>.size // 1
struct Point { var x: Double; var y: Double }
MemoryLayout<Point>.size // 16
尺寸有三種,各自代表不同意義。
size 是單一值連續的記憶體 footprint,不包含型別末端為對齊而產生的 tail padding。
**alignment(對齊)**是型別可放置之位址的規則。CPU 從 8 的倍數位址讀取 8 位元組值會更有效率(某些架構甚至是必要條件)。
因此每種型別都有「必須放在 n 的倍數位址」的限制。以下數值以目前的 64 位元 Apple 平台為準:Double 的 alignment 是 8,Bool 是 1。
**stride(步幅)**是陣列中到下一個元素的距離。對齊限制可能在元素間產生填充,因此 stride ≥ size。
這是展示三種尺寸關係的經典範例。
struct Bad {
var flag: Bool // 1 位元組
var value: Double // 8 必須放在 — 8 位元組的倍數位址上
var count: Int32 // 4 位元組
}
MemoryLayout<Bad>.stride // 24
struct Good {
var value: Double // 8
var count: Int32 // 4
var flag: Bool // 1
}
MemoryLayout<Good>.stride // 16
內容相同,但只改變順序,陣列就能節省 33%。Bad 需要在 flag 後對齊 value,因此要 7 位元組填充(Swift ABI Type Layout)。
實務上,從 alignment 較大的屬性開始分組,是減少填充的常見啟發式方法。但它不保證對所有型別都最佳,最終結果仍應用 MemoryLayout 測量。
目前 Swift 的 fragile struct 配置演算法會依宣告順序放置儲存屬性。但欄位順序不是語言或 ABI 的保證(Swift ABI · SE-0260)。需要精確 offset 的程式碼不可依賴觀察值。
其次,這項最佳化只有在執行個體很多時才有意義。設定物件單個 8 位元組不同,100 萬元素陣列每個元素省 8 位元組就是總共 8 MB(Swift ABI Type Layout)。
一如往常,先進行量測。
enum 的魔法 — Optional 標籤免費的時候
enum 的配置展現 Swift 編譯器的巧妙之處。必須儲存 case 標籤,因此會把標籤藏在 payload 型別未使用的位元模式(extra inhabitants)中。
代表案例是 Bool?。目前的 64 位元 Apple 平台上,MemoryLayout<Bool?>.size 是 1。
Bool 在 1 位元組中只使用兩種模式(0、1),因此可用剩餘模式表示 nil。
參考型別 Optional 也一樣。Swift 類別指標不會把 null 當成物件,因此用 0 表示 nil。目前 64 位元環境下,AnyObject? 的 size 是 8。
但不是所有 Optional 都免費。Int使用 8 位元組的所有位元模式表示值。因此目前環境中,Int?的 size 是 9,stride 是 16(Swift ABI)。
Optional 篇所說的「安全性幾乎免費」,對能重用多餘表示的型別而言是準確的。Optional 語法本身不保證所有型別的記憶體成本為零。
實測盒子 — 單一協定 existential 是 5 個 word
some·any 篇只說過「any 是有成本的盒子」,現在可以實測了。
protocol Shape {}
MemoryLayout<any Shape>.size // 40 — 目前 64位元環境
沒有類別限制的單一協定 any Shape,在目前 64 位元環境中是 40 位元組,也就是 5 個 word:3 個 word 的值緩衝區、型別中繼資料指標,以及一個 witness table 指標。
關鍵分歧在於所容納值的 size 與 alignment 是否都符合 3 個 word 的內嵌緩衝區條件。
超過條件時,值會配置到獨立儲存空間,緩衝區中則放置指標。此時可能增加堆積配置、參考計數與間接存取成本(Swift ABI · SE-0335)。
5 個 word 不是所有 any 型別的固定大小。協定組合可能增加 witness table 指標。
類別專用協定 existential 使用更小的配置,不需要值緩衝區和額外的中繼資料指標。
若協定導向程式碼的效能出乎預期,可以考慮用 some·泛型移除盒子,或縮小容納的型別。實際效果必須用 Instruments 與基準測試確認(SE-0335 Existential any)。
也來看看類別執行個體的尺寸。目前 64 位元 Apple 平台上,類別型別值的 stride 是 8,代表參考的大小,不是物件大小。
原生 Swift 堆積物件通常有包含中繼資料指標和內嵌參考計數資訊的標頭,64 位元下為 16 位元組。實際配置大小可能因屬性對齊和配置器捨入而更大(Swift HeapObject · Swift RefCount)。
ARC 篇提到的參考型別隱藏成本,就是這個標頭,以及配置、釋放和計數操作。若把數十萬個小資料片段做成類別,管理成本可能超過資料本身。
實務工具箱 — 重新量測、確認、保持懷疑
整理處理這一層的實務工具。
**用 MemoryLayout 驗證假設。**設計大量產生的模型 struct 時,養成測量一次 stride 的習慣就很有幫助。
也可以像 #expect(MemoryLayout<Tick>.stride <= 32) 一樣在單元測試中加入預算。之後新增屬性超過預算時,就會立即發現。
**用 Instruments Allocations 檢查堆積。**如果明明以 struct 為主卻有大量堆積配置,嫌疑對象很明確。
any 盒子、閉包捕獲(閉包篇)、CoW(copy-on-write)儲存,以及類別執行個體。Allocations 的依型別統計會告訴你是哪一類。
**copy-on-write 集合的錯覺:**目前 64 位元 Apple 平台上,MemoryLayout<[Int]>.stride是 8。Array 值是間接參考堆積儲存的小型 struct,但不可把這個數值視為固定 ABI。
這就是為什麼含有陣列的 struct stride 很小,也不能下結論說它「很輕」。
真正的重量在堆積儲存中,而 Allocations 會顯示它。CoW 原理已在另一篇文章中說明。
最後是平衡感。這一篇的知識並非每天都會用到。
但知道「為什麼」的價值會在問題情境中顯現。記憶體圖異常時,可以從嫌疑清單而不是猜測開始,這就是深入一層的優勢。
總結
- 型別有三種尺寸:size 是不含 tail padding 的 footprint,alignment 是位址對齊規則,stride 是連續配置間距。大量產生的 struct 將 alignment 大的屬性分組,通常能減少填充,但必須實測。
- enum 可以把標籤藏在 payload 的 extra inhabitant 中。Bool? 和參考型別 Optional 不需額外空間,但 Int? 等型別需要獨立的標籤空間。
- 64 位元單一非類別協定 existential 是 5 個 word。若值的 size 或 alignment 超過 3 個 word 的緩衝區條件,可能產生獨立儲存空間和間接成本。
- 目前 64 位元原生 Swift 物件的基本標頭通常是 16 位元組。實際配置大小可能依屬性與 allocator 對齊而增加。
- 工具是 MemoryLayout(設計驗證)與 Instruments Allocations(實測)。沒有量測的配置最佳化就是過早最佳化。
下一篇討論編譯期魔法:Swift 巨集,也就是 #Preview 和 @Observable 背後讓程式碼撰寫程式碼的機制。
來源與確認依據
- MemoryLayout — Apple Developer Documentation · 確認 2026-08-19 · 依據:size、alignment、stride 的 API 定義
- Swift ABI Type Layout — Swift 專案 · 官方文件 · 確認 2026-08-19 · 依據:struct、enum、existential container 配置
- SE-0260 Library Evolution — Swift Evolution · 確認 2026-08-19 · 依據:struct 欄位順序的語言與 ABI 保證範圍
- SE-0335 Existential any — Swift Evolution · 確認 2026-08-19 · 依據:existential 的 3 word 內嵌緩衝區與動態記憶體成本
- Swift HeapObject · Swift RefCount — Swift Runtime · 確認 2026-08-19 · 依據:原生堆積物件標頭與內嵌參考計數

![[Swift 深入解析 #6] Swift 記憶體配置:屬性順序會改變大小 封面圖](/assets/images/posts/dfd50aa0-6496-4f00-a59f-21b04c18ed87/swift-memory-layout-size-stride-alignment.jpg)