数据结构对齐

**資料結構對齊**是程式編譯後資料在記憶體內的佈局與使用方式。包括三方面內容：**數據對齊**、**資料結構填充**（padding）與**包入**（packing）。

現代計算機 CPU 一般是以 32 位元或 64 位元大小作地址對齊，以 32 位元架構的計算機舉例，每次以連續的 4 位元組為一個區間，第一個位元組的位址位在每次 CPU 抓取資料大小的邊界上，除此之外，如果要訪問的變量沒有對齊，可能會觸發匯流排錯誤。

當資料小於計算機的字（word）尺寸，可能把幾個資料放在一個字中，稱為**包入**（packing）。

許多程式語言自動處理資料結構對齊。Ada 語言， PL/I, Pascal, 某些 C 語言與 C++ 實現，D 語言，Rust, 與彙編語言允許特別控制對齊的方式。

## 定義

內存地址 _a_ 被稱為 _n 字節對齊_，_a_ 是 _n_ 的倍數（_n_ 應是 2 的冪），也可以理解為當被訪問的數據長度為 _n_ 字節時，數據地址為 _n_ 字節對齊。如果內存未對齊，稱作 _misaligned_。

內存指針是對齊的，如果它所指的數據是對齊的。指向聚合數據（aggregate data，如 struct 或數組）是對齊的，若且唯若它的每個組成數據是對齊的。

## 體系結構

### RISC

大多數 RISC 處理器在加載或存儲指令訪問錯位的地址時，會產生一個對齊錯誤。這允許作業系統使用其他指令來模擬錯位的訪問。例如，對齊錯誤處理程序可以使用字節加載或存儲（其總是對齊的）來模擬更大的加載或存儲指令。

一些架構，如 MIPS 架構有特殊的無對齊加載和存儲指令。一條無對齊的加載指令從具有最低字節地址的內存字中獲取字節，另一條指令從具有最高字節地址的內存字中獲取字節。同樣的，store-high 和 store-low 指令分別在較高和較低的內存字中存儲相應的字節。

DEC Alpha 架構對不對齊的加載和存儲採用了兩步法。第一步是將上層和下層的內存字加載到獨立的暫存器中。第二步是使用類似於 MIPS 指令的特殊低 / 高指令來提取或修改內存字。通過將修改後的內存字存儲到內存中，就完成了一個無對齊存儲。造成這種複雜性的原因是，最初的 Alpha 架構只能讀取或寫入 32 位或 64 位的值。這被證明是一個嚴重的限制，經常導致代碼臃腫和性能不佳。為了解決這個限制，在最初的架構中加入了一個名為 "字節字擴展"（BXW）的擴展。它包括字節和字的加載和存儲指令。

因為這些指令比正常的內存加載和存儲指令更大、更慢，所以只有在必要時才可以使用它們。一些 C 和 C++ 編譯器有一個 "無對齊" 屬性，可以應用於需要無對齊指令的指針。

### x86

x86 體系架構最初是不要求內存對齊。一些 SSE2 指令要求數據是 128 比特 (16 位元組) 對齊。有些 CPU 指令用於未對齊訪問如 MOVDQU。讀寫內存操作僅在對齊時才是原子的。

## C 語言 struct 在 x86 上的對齊

C 語言資料結構內的成員按照先後順序在內存中存儲。

### 默認對齊

結構體中每個成員的類型通常有一個默認的對齊方式，也就是說，除非程式設計師另有要求，否則它將在一個預先確定的邊界上對齊。以下典型的對齊方式對微軟（Visual C++）、Borland/CodeGear（C++Builder）、Digital Mars（DMC）和 GNU（GCC）的編譯器在為 32 位 x86 編譯時有效。

一個 **char**（一個字節）變量將被 1 位元組對齊。

一個 **short**（兩個字節）變量將是 2 位元組對齊的。

一個 **int**（四個字節）變量將是 4 位元組對齊的。

一個 **long**（四個字節）變量將被 4 位元組對齊。

一個 **float**（四個字節）變量將是 4 位元組對齊的。

一個 **double**（8 個字節）變量在 Windows 上是 8 位元組對齊的，在 Linux 上是 4 位元組對齊的（用 - malign-double 編譯時選項是 8 位元組）。

一個 **long long**（8 個字節）變量將被 4 位元組對齊。

一個 **long double**（C++Builder 和 DMC 為 10 個字節，Visual C++ 為 8 個字節，GCC 為 12 個字節）變量在 C++Builder 上將是 8 位元組對齊，DMC 為 2 位元組對齊，Visual C++ 為 8 位元組對齊，GCC 為 4 位元組對齊。

任何**指針**（四個字節）都將是 4 位元組對齊的。(例如：`char*`, `int*`)

與 32 位系統相比，LP64 64 位系統在對齊方面唯一值得注意的區別是。

一個 **long**（八個字節）變量將是 8 位元組對齊的。

一個 **double**（8 個字節）變量將是 8 位元組對齊的。

一個 **long long**（8 個字節）變量將是 8 位元組對齊的。

一個 **long double**（在 Visual C++ 中是 8 個字節，在 GCC 中是 16 個字節）變量在 Visual C++ 中是 8 位元組對齊的，在 GCC 中是 16 位元組對齊的。

任何**指針**（八個字節）變量都將是 8 位元組對齊的。

有些數據類型取決於實現方式。

### 指定對齊

一些編譯器（Microsoft, Borland, GNU, 等等）使用#pragma directive 指定對齊的包入（packing）。例如：

這個結構在 32 位系統的大小為 6 位元組。

### 預設 packing 與#pragma pack

Microsoft 編譯器的項目預設 packing (編譯選項 / Zp) 與#pragma pack 指令。#pragma pack 指令僅能減少 packing 尺寸。

## 參見

* 數組步長
* 類型雙關
* offsetof

## 參考文獻

* Bryant, Randal E.; David, O'Hallaron. Computer Systems: A Programmer's Perspective 2003. Upper Saddle River, NJ: Pearson Education. 2003 [2017-11-21]. ISBN0-13-034074-X. （原始內容存檔於 2007-08-06）.

## 外部連結

* IBM developerWorks article on data alignment （頁面存檔備份，存於網際網路檔案館）
* Article on data alignment and performance （頁面存檔備份，存於網際網路檔案館）
* MSDN article on data alignment
* Article on data alignment and data portability （頁面存檔備份，存於網際網路檔案館）
* Byte Alignment and Ordering （頁面存檔備份，存於網際網路檔案館）
* Intel Itanium Architecture Software Developer's Manual （頁面存檔備份，存於網際網路檔案館）
* Data Alignment when Migrating to 64-Bit Intel® Architecture （頁面存檔備份，存於網際網路檔案館）
* PowerPC Microprocessor Family: The Programming Environments for 32-Bit Microprocessors