資料對齊(data alignment)
資料對齊是一條關於多位元組值「可以從記憶體何處開始」的規則。記憶體是一長排位元組大小的格子,每個都有位址,而多數處理器偏好(或堅持)一個值坐在其大小倍數的位址上:4 位元組整數放在可被 4 整除的位址、8 位元組值放在可被 8 整除的位址。想像為特定車型畫好的停車格——一輛巴士必須停進標示巴士的格子,不能橫跨兩個汽車格——那種對齊就是 alignment。
原因在於硬體實際擷取記憶體的方式。處理器以固定大小的區塊讀取記憶體(一個字組或一條快取列),所以對齊的 4 位元組整數俐落地落在一個區塊內、一次存取就抓到。橫跨兩個區塊的未對齊值,可能需要兩次讀取再加上把碎片縫起來的額外工作——較慢,而且在某些處理器上根本不被允許(未對齊存取會引發硬體錯誤、使程式當機)。為了保持對齊,編譯器會悄悄在結構的欄位之間插入未使用的間隙位元組,稱為填補(padding)。這就是為什麼一個結構的總大小可能大於它欄位大小的總和,也是為什麼把欄位由大到小重排能讓它變小。
對齊之所以重要,是因為它是效能與正確性兩方面的隱藏因素。多數時候編譯器替你處理好,你從不察覺。但它會在真實情境中冒出來:安排資料結構以最小化浪費的填補、把原始位元組緩衝區轉型成有型別的指標(在嚴格的硬體上可能造成未對齊存取而當機)、以及排列陣列使常用欄位共享一條快取列。誠實的一點是:對齊不是可有可無的裝飾——它反映了記憶體如何被擷取的物理現實,忽視它會付出速度代價,或在不寬容的處理器上導致當機。
一個裝著 1 位元組 char、接著 4 位元組 int 的結構不是 5 位元組:編譯器在 char 之後插入 3 個填補位元組,使 int 從可被 4 整除的位址開始,讓結構變成 8 位元組。把 int 排在 char 之前可消除那個間隙。
填補位元組讓每個欄位保持對齊,所以結構可能大於其欄位大小的總和。
在嚴格的處理器上,未對齊存取不只是慢——它會引發錯誤而當機。把原始位元組緩衝區轉型成有型別的指標是常見的觸發方式,所以對齊是正確性問題、不只是效能問題。