1. 程式人生 > >從記憶體的角度解釋記憶體對齊的原理

從記憶體的角度解釋記憶體對齊的原理

大部分的參考資料都是如是說的:

1、平臺原因(移植原因):不是所有的硬體平臺都能訪問任意地址上的任意資料的;某些硬體平臺只能在某些地址處取某些特定型別的資料,否則丟擲硬體異常。

2、效能原因:資料結構(尤其是棧)應該儘可能地在自然邊界上對齊。原因在於,為了訪問未對齊的記憶體,處理器需要作兩次記憶體訪問;而對齊的記憶體訪問僅需要一次訪問。



二、對齊規則

每個特定平臺上的編譯器都有自己的預設“對齊係數”(也叫對齊模數)。程式設計師可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊係數”。

規則:

1、資料成員對齊規則:結構(struct)(或聯合(union))的資料成員,第一個資料成員放在offset為0的地方,以後每個資料成員的對齊按照#pragma pack指定的數值和這個資料成員

自身長度中,比較小的那個進行。

2、結構(或聯合)的整體對齊規則:在資料成員完成各自對齊之後,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大資料成員長度中,比較小的那個進行。

3、結合1、2可推斷:當#pragma pack的n值等於或超過所有資料成員長度的時候,這個n值的大小將不產生任何效果。



三、試驗

下面我們通過一系列例子的詳細說明來證明這個規則

編譯器:GCC 3.4.2、VC6.0

平臺:Windows XP



典型的struct對齊

struct定義:

#pragma pack(n) /* n = 1, 2, 4, 8, 16 */

struct test_t {

int a;

char b;

short c;

char d;

};

#pragma pack(n)

首先確認在試驗平臺上的各個型別的size,經驗證兩個編譯器的輸出均為:

sizeof(char) = 1

sizeof(short) = 2

sizeof(int) = 4



試驗過程如下:通過#pragma pack(n)改變“對齊係數”,然後察看sizeof(struct test_t)的值。



1、1位元組對齊(#pragma pack(1))

輸出結果:sizeof(struct test_t) = 8 [兩個編譯器輸出一致]

分析過程:

1) 成員資料對齊

#pragma pack(1)

struct test_t {

int a; /* 長度4 > 1 按1對齊;起始offset=0 0%1=0;存放位置區間[0,3] */

char b; /* 長度1 = 1 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */

short c; /* 長度2 > 1 按1對齊;起始offset=5 5%1=0;存放位置區間[5,6] */

char d; /* 長度1 = 1 按1對齊;起始offset=7 7%1=0;存放位置區間[7] */

};

#pragma pack()

成員總大小=8



2) 整體對齊

整體對齊係數 = min((max(int,short,char), 1) = 1

整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 8 /* 8%1=0 */ [注1]



2、2位元組對齊(#pragma pack(2))

輸出結果:sizeof(struct test_t) = 10 [兩個編譯器輸出一致]

分析過程:

1) 成員資料對齊

#pragma pack(2)

struct test_t {

int a; /* 長度4 > 2 按2對齊;起始offset=0 0%2=0;存放位置區間[0,3] */

char b; /* 長度1 < 2 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */

short c; /* 長度2 = 2 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */

char d; /* 長度1 < 2 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */

};

#pragma pack()

成員總大小=9

2) 整體對齊

整體對齊係數 = min((max(int,short,char), 2) = 2

整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 10 /* 10%2=0 */



3、4位元組對齊(#pragma pack(4))

輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]

分析過程:

1) 成員資料對齊

#pragma pack(4)

struct test_t {

int a; /* 長度4 = 4 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */

char b; /* 長度1 < 4 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */

short c; /* 長度2 < 4 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */

char d; /* 長度1 < 4 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */

};

#pragma pack()

成員總大小=9



2) 整體對齊

整體對齊係數 = min((max(int,short,char), 4) = 4

整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 12 /* 12%4=0 */



4、8位元組對齊(#pragma pack(8))

輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]

分析過程:

1) 成員資料對齊

#pragma pack(8)

struct test_t {

int a; /* 長度4 < 8 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */

char b; /* 長度1 < 8 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */

short c; /* 長度2 < 8 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */

char d; /* 長度1 < 8 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */

};

#pragma pack()

成員總大小=9

2) 整體對齊

整體對齊係數 = min((max(int,short,char), 8) = 4

整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 12 /* 12%4=0 */



5、16位元組對齊(#pragma pack(16))

輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]

分析過程:

1) 成員資料對齊

#pragma pack(16)

struct test_t {

int a; /* 長度4 < 16 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */

char b; /* 長度1 < 16 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */

short c; /* 長度2 < 16 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */

char d; /* 長度1 < 16 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */

};

#pragma pack()

成員總大小=9



2) 整體對齊

整體對齊係數 = min((max(int,short,char), 16) = 4

整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 12 /* 12%4=0 */

8位元組和16位元組對齊試驗證明了“規則”的第3點:“當#pragma pack的n值等於或超過所有資料成員長度的時候,這個n值的大小將不產生任何效果”。




記憶體分配與記憶體對齊是個很複雜的東西,不但與具體實現密切相關,而且在不同的作業系統,編譯器或硬體平臺上規則也不盡相同,雖然目前大多數系統/語言都具有自動管理、分配並隱藏低層操作的功能,使得應用程式編寫大為簡單,程式設計師不在需要考慮詳細的記憶體分配問題。但是,在系統或驅動級以至於高實時,高保密性的程式開發過程中,程式記憶體分配問題仍舊是保證整個程式穩定,安全,高效的基礎。