標籤:
在最近的項目中,我們涉及到了“記憶體對齊”技術。對於大部分程式員來說,“記憶體對齊”對他們來說都應該是“透明的”。“記憶體對齊”應該是編譯器的“管轄範圍”。編譯器為程式中的每個“資料單元”安排在適當的位置上。但是C語言的一個特點就是太靈活,太強大,它允許你幹預“記憶體對齊”。如果你想瞭解更加底層的秘密,“記憶體對齊”對你就不應該再透明了。
一、記憶體對齊的原因 大部分的參考資料都是如是說的: 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的C編譯器,平台為Windows XP + Sp2。
我們將用典型的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值的大小將不產生任何效果”。另外記憶體對齊是個很複雜的東西,上面所說的在有些時候也可能不正確。呵呵^_^
[注1] 什麼是“圓整”? 舉例說明:如上面的8位元組對齊中的“整體對齊”,整體大小=9 按 4 圓整 = 12 圓整的過程:從9開始每次加一,看是否能被4整除,這裡9,10,11均不能被4整除,到12時可以,則圓整結束。
c語言中記憶體對齊問題