基本介紹
詞條簡介,詳細解釋,對齊原因,對齊規則,驗證試驗,基本結論,
詞條簡介
對於大部分程式設計師來說,“記憶體對齊”對他們來說都應該是“透明的”。
詳細解釋
對齊原因
大部分的參考資料都是如是說的:
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值的大小將不產生任何效果。
1)結構體變數的首地址是其最長基本類型成員的整數倍;
備註:編譯器在給結構體開闢空間時,首先找到結構體中最寬的基本數據類型,然後尋找記憶體地址能是該基本數據類型的整倍的位置,作為結構體的首地址。將這個最寬的基本數據類型的大小作為上面介紹的對齊模數。
2)結構體每個成員相對於結構體首地址的偏移量(offset)都是成員大小的整數倍,如有需要編譯器會在成員之間加上填充位元組(internal adding);
備註:為結構體的一個成員開闢空間之前,編譯器首先檢查預開闢空間的首地址相對於結構體首地址的偏移是否是本成員的整數倍,若是,則存放本成員,反之,則在本成員和上一個成員之間填充一定的位元組,以達到整數倍的要求,也就是將預開闢空間的首地址後移幾個位元組。
備註:
a、結構體總大小是包括填充位元組,最後一個成員滿足上面兩條以外,還必須滿足第三條,否則就必須在最後填充幾個位元組以達到本條要求。
b、如果結構體記憶體在長度大於處理器位數的元素,那么就以處理器的倍數為對齊單位;否則,如果結構體內的元素的長度都小於處理器的倍數的時候,便以結構體裡面最長的數據元素為對齊單位。
4) 結構體內類型相同的連續元素將在連續的空間內,和數組一樣。
驗證試驗
我們通過一系列例子的詳細說明來證明這個規則吧!
我試驗用的編譯器包括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[6];
};
#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) = 13[兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(1)
struct test_t {
int a; /* int型,長度4 > 1 按1對齊;起始offset=0 0%1=0;存放位置區間[0,3] */
char b; /* char型,長度1 = 1 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* short型,長度2 > 1 按1對齊;起始offset=5 5%1=0;存放位置區間[5,6] */
char d[6]; /* char型,長度1 = 1 按1對齊;起始offset=7 7%1=0;存放位置區間[7,C] */
};/*char d[6]要看成6個char型變數*/
#pragma pack()
成員總大小=13
2) 整體對齊
整體對齊係數 = min((max(int,short,char), 1) = 1
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 13 /*13%1=0*/ [注1]
2、2位元組對齊(#pragma pack(2))
輸出結果:sizeof(struct test_t) = 14 [兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(2)
struct test_t {
int a; /* int型,長度4 > 2 按2對齊;起始offset=0 0%2=0;存放位置區間[0,3] */
char b; /* char型,長度1 < 2 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* short型,長度2 = 2 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
char d[6]; /* char型,長度1 < 2 按1對齊;起始offset=8 8%1=0;存放位置區間[8,D] */
};
#pragma pack()
成員總大小=14
2) 整體對齊
整體對齊係數 = min((max(int,short,char), 2) = 2
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 14 /* 14%2=0 */
3、4位元組對齊(#pragma pack(4))
輸出結果:sizeof(struct test_t) = 16 [兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(4)
struct test_t {
int a; /* int型,長度4 = 4 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */
char b; /* char型,長度1 < 4 按1對齊;起始offset=4 4%4=0;存放位置區間[4] */
short c; /*short型, 長度2 < 4 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
char d[6]; /* char型,長度1 < 4 按1對齊;起始offset=8 8%1=0;存放位置區間[8,D] */
};
#pragma pack()
成員總大小=14
2) 整體對齊
整體對齊係數 = min((max(int,short,char), 4) = 4
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 16 /*16%4=0*/
4、8位元組對齊(#pragma pack(8))
輸出結果:sizeof(struct test_t) = 16 [兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(8)
struct test_t {
int a; /* int型,長度4 < 8 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */
char b; /* char型,長度1 < 8 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* short型,長度2 < 8 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
char d[6]; /* char型,長度1 < 8 按1對齊;起始offset=8 8%1=0;存放位置區間[8,D] */
};
#pragma pack()
成員總大小=14
2) 整體對齊
整體對齊係數 = min((max(int,short,char), 8) = 4
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 16 /*16%4=0*/
5、16位元組對齊(#pragma pack(16))
輸出結果:sizeof(struct test_t) = 16 [兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(16)
struct test_t {
int a; /* int型,長度4 < 16 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */
char b; /* char型,長度1 < 16 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* short型,長度2 < 16 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
char d[6]; /* char型,長度1 < 16 按1對齊;起始offset=8 8%1=0;存放位置區間[8,D] */
};
#pragma pack()
成員總大小=14
2) 整體對齊
整體對齊係數 = min((max(int,short,char), 16) = 4
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 16 /*16%4=0*/
基本結論
8位元組和16位元組對齊試驗證明了“規則”的第3點:“當#pragma pack的n值等於或超過所有數據成員長度的時候,這個n值的大小將不產生任何效果”。另外記憶體對齊是個很複雜的東西,讀者不妨把上述結構體中加個double型成員進去練習一下,上面所說的在有些時候也可能不正確。呵呵^_^
[注1]
什麼是“圓整”?
舉例說明:如上面的8位元組對齊中的“整體對齊”,整體大小=9 按 4 圓整 = 12
圓整的過程:從9開始每次加一,看是否能被4整除,這裡9,10,11均不能被4整除,到12時可以,則圓整結束。
上面文字表述太不直觀了,鄙人給段代碼直觀的體現出來,代碼如下:
#pragma pack(4) /* n = 1, 2, 4, 8, 16 */
struct test_t{
int a;
char b;
short c;
char d[6];
}ttt;
void print_hex_data(char *info, char *data, int len)
{
int i;
dbg_printf("%s:\n\r", info);
for(i = 0; i < len; i++){
dbg_printf("%02x ", (unsigned char)data[i]);
if (0 == ((i+1) % 32))
dbg_printf("\n");
}
dbg_printf("\n\r");
}
int main()
{
ttt.a = 0x1a2a3a4a;
ttt.b = 0x1b;
ttt.c = 0x1c2c;
char *s = "123456";
memcpy(ttt.d, s, 6);
print_hex_data("struct_data", (char *)&ttt, sizeof(struct test_t));
return 0;
}
struct test_t{
int a;
char b;
short c;
char d[6];
}ttt;
void print_hex_data(char *info, char *data, int len)
{
int i;
dbg_printf("%s:\n\r", info);
for(i = 0; i < len; i++){
dbg_printf("%02x ", (unsigned char)data[i]);
if (0 == ((i+1) % 32))
dbg_printf("\n");
}
dbg_printf("\n\r");
}
int main()
{
ttt.a = 0x1a2a3a4a;
ttt.b = 0x1b;
ttt.c = 0x1c2c;
char *s = "123456";
memcpy(ttt.d, s, 6);
print_hex_data("struct_data", (char *)&ttt, sizeof(struct test_t));
return 0;
}
#pragma pack(1)的結果:
4a 3a 2a 1a 1b 2c 1c 31 32 33 34 35 36
#pragma pack(2)的結果:
4a 3a 2a 1a 1b 00 2c 1c 31 32 33 34 35 36
#pragma pack(4)的結果:
4a 3a 2a 1a 1b 00 2c 1c 31 32 33 34 35 36 00 00
#pragma pack(8)的結果:
4a 3a 2a 1a 1b 00 2c 1c 31 32 33 34 35 36 00 00
#pragma pack(16)的結果:
4a 3a 2a 1a 1b 00 2c 1c 31 32 33 34 35 36 00 00