什么是內存對齊
現代計算機中內存空間都是按照byte划分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定變量的時候經常在特定的內存地址訪問,這就需要各類型數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
為什么要內存對齊
平台原因:
某些硬件平台只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。
性能原因:
為了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。
總的來說,內存對齊就是拿空間換取時間的做法,目的是為了讓CPU能一次獲取到數據,從而提升性能。
#pragma pack()
該預處理指令用來改變對齊參數。在缺省情況下,C編譯器為每一個變量或數據單元按其自然對界條件分配空間。一般地,可以通過下面的方法來改變缺省的對齊參數:
· 使用偽指令#pragma pack (n),C編譯器將按照n字節對齊。
· 使用偽指令#pragma pack (),取消自定義字節對齊方式。
也可以寫成:
#pragma pack(push,n)
#pragma pack(pop)
#pragma pack (n)表示每個成員的對齊單元不大於n(n為2的整數次冪)。這里規定的是上界,只影響對齊單元大於n的成員,對於對齊字節不大於n的成員沒有影響。
內存對齊規則
每個特定平台上的編譯器都有自己的默認“對齊系數”(也叫對齊模數)。程序員可以通過預編譯命令#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值的大小將不產生任何效果。
內存對齊實例
#include <stdio.h> #include <stdint.h> #pragma pack(1) typedef struct { /*成員對齊*/ int a; //長度4 < 1 **按1對齊**;偏移量為0;存放位置區間[0,3] char b; //長度1 = 1 **按1對齊**;偏移量為4;存放位置區間[4] short c; //長度2 > 1 **按1對齊**;偏移量為5;存放位置區間[5,6] char d; //長度1 = 1 **按1對齊**;偏移量為6;存放位置區間[7] /*整體對齊*/ //整體對齊系數 = min(對齊系數1,最大成員長度4) = 1,無需對齊,整體大小為8; }test_pack1; #pragma pack() #pragma pack(2) typedef struct {
/*成員對齊*/ int a; //長度4 > 2 **按2對齊**;偏移量為0;存放位置區間[0,3] char b; //長度1 < 2 **按1對齊**;偏移量為4;存放位置區間[4] short c; //長度2 = 2 **按2對齊**;偏移量要提升到2的倍數6;存放位置區間[6,7] char d; //長度1 < 2 **按1對齊**;偏移量為7;存放位置區間[8];共九個字節 /*整體對齊*/ //整體對齊系數 = min(對齊系數2,最大成員長度4) = 2,將9提升到2的倍數10,整體大小為10; }test_pack2; #pragma pack() #pragma pack(4) typedef struct {
/*成員對齊*/ int a; //長度4 = 4 **按4對齊**;偏移量為0;存放位置區間[0,3] char b; //長度1 < 4 **按1對齊**;偏移量為4;存放位置區間[4] short c; //長度2 < 4 **按2對齊**;偏移量要提升到2的倍數6;存放位置區間[6,7] char d; //長度1 < 4 **按1對齊**;偏移量為7;存放位置區間[8];總大小為9 /*整體對齊*/ //整體對齊系數 = min(對齊系數4,最大成員長度4) = 4,將9提升到4的倍數12,整體大小為12; }test_pack4; #pragma pack() #pragma pack(8) typedef struct {
/*成員對齊*/ int a; //長度4 < 8 **按4對齊**;偏移量為0;存放位置區間[0,3] char b; //長度1 < 8 **按1對齊**;偏移量為4;存放位置區間[4] short c; //長度2 < 8 **按2對齊**;偏移量要提升到2的倍數6;存放位置區間[6,7] char d; //長度1 < 8 **按1對齊**;偏移量為7;存放位置區間[8],總大小為9 /*整體對齊*/ //整體對齊系數 = min(對齊系數8,最大成員長度4) = 4,將9提升到4的倍數12,整體大小為12; }test_pack8; #pragma pack() test_pack1 pack1 = {0x11111111, 0x22, 0x3333, 0x44}; uint8_t *ptrPack1 = (uint8_t *)&pack1; test_pack2 pack2 = {0x11111111, 0x22, 0x3333, 0x44}; uint8_t *ptrPack2 = (uint8_t *)&pack2; test_pack4 pack4 = {0x11111111, 0x22, 0x3333, 0x44}; uint8_t *ptrPack4 = (uint8_t *)&pack4; test_pack8 pack8 = {0x11111111, 0x22, 0x3333, 0x44}; uint8_t *ptrPack8 = (uint8_t *)&pack8; int main(int argc, char *argv[]) { int i = 0; printf("#pragma pack(1) \tsize:%2d,\t", sizeof(pack1)); for(i=0; i<sizeof(pack1); i++) { printf("%02x", ptrPack1[i]); } printf("\r\n"); printf("#pragma pack(2) \tsize:%2d,\t", sizeof(pack2)); for(i=0; i<sizeof(pack2); i++) { printf("%02x", ptrPack2[i]); } printf("\r\n"); printf("#pragma pack(4) \tsize:%2d,\t", sizeof(pack4)); for(i=0; i<sizeof(pack4); i++) { printf("%02x", ptrPack4[i]); } printf("\r\n"); printf("#pragma pack(8) \tsize:%d,\t", sizeof(pack8)); for(i=0; i<sizeof(pack8); i++) { printf("%02x", ptrPack8[i]); } printf("\r\n");
return 0; }
該段測試實例測試同一結構體,為了觀察方便,成員的每一個字節值按順序分別為十六進制0xaa、0xbb、0xcc、0xdd。
分別進行1、2、4、8字節對齊方式對齊,計算其占用內存大小及在內存的排列的結果,具體對齊步驟見代碼注釋部分。
什么時候需要指定內存對齊
一般情況下都不需要對編譯器進行的內存對齊規則進行修改,因為這樣會降低程序的性能,只有這個結構在涉及到對外交互的情況下,比如這個結構需要對外協議交互、寫入文件等。