什麼是記憶體對齊?
為什麼結構相同,記憶體大小卻不同?
該部落格上撰寫的文章是出於教學目的而創建的。 另外,文章中的資訊可能不正確,請謹慎。
在編碼時,有時結構的大小與我的預期不同。這種現象正式稱為記憶體對齊和填充。
CPU從記憶體讀取變數時,並不是以1位元組為單位讀取的。 由於它是按區塊、逐字(通常是 4 位元組或 8 位元組)讀取的,因此變數在記憶體中的排列方式對於效能極為重要。
結構的總體大小可能會根據結構的成員在記憶體中的排列方式而變化。 这是由于填充的概念。填充是編譯器自動插入的空白空間,以幫助將結構成員與 CPU 上的字邊界對齊。 由於中間有一個空的空間,結構的尺寸自然會增加。
我們自己用程式碼來驗證一下。
#include <iostream>
#include <cstddef> //使用offsetof宏
//1.記憶體佈局效率低(共24位元組)
struct BadOrder {
int A; //4 個位元組,下一個變數需要 8 個位元組對齊,從而產生 4 個位元組的填充(總共使用 8 個位元組)。
long long B; //8個字節,單獨使用8個位元組。 (總共使用了 16 個位元組)
bool C; //使用 1 位元組後會發生 1 位元組、7 位元組填充(總共使用 24 位元組)。
};
//2.考慮編譯器優化的高效能記憶體放置(共16位元組)
struct GoodOrder {
long long A; //8個字節,單獨使用8個位元組。 (總共使用了 8 個位元組)
int B; //4個位元組,剩下的4個位元組可以使用後面的變量,所以不會發生填充。 (總共使用了 12 個位元組)
bool C; //使用 1 位元組後會發生 1 位元組、3 位元組填充。 (總共使用了 16 個位元組)
};
//3. 強制去除填充的結構(共13位元組)
#pragma pack(push, 1)
struct PackedStruct {
int A; // 4 Byte
long long B; // 8 Byte
bool C; // 1 Byte
};
#pragma pack(pop)
這裡出現一個問題。為什麼BadOrder 末尾額外有7 個位元組的填充,GoodOrder 末尾有3 個位元組的填充? 這是由於結構分組為數組等時的對齊要求。 結構體的總大小必須是內部成員中最大資料型態(上例中的 8 位元組 long long)的倍數。 這是因為移動到數組的下一個元素時可以保證相同的 8 位元組對齊。
第三步是使用#pragma pack 強制刪除填充。 這只能節省 13 個位元組的內存,但可能會導致致命的效能下降。 如果CPU逐字讀取且資料的起始點未對齊, CPU必須讀取記憶體兩次並執行無用的額外操作才能強制合併。在某些硬體環境下,程式甚至可能崩潰。
此外,快取行效率也不容忽視。 現代CPU通常以64位元組為單位導入整個內存,但如果像數字1那樣是24字節,那麼64位元組只能儲存2。 如果將其優化為 16 位元組(如數字 2 所示),則所有 4 個位元組都將完全適合。循環數組時,處理速度有巨大差異。
除非有特殊情況要求記憶體佈局必須準確無1位元組錯誤,例如網路通訊標準或檔案輸入/輸出, 在一般情況下,允許填充具有更大的性能優勢。 因此,最好養成透過從最大尺寸開始(或以相反順序)對成員進行分組來最小化不必要填充的習慣,如數字 2 所示。