什么是字節對齊,為什么要對齊?
現代計算機中內存空間都是按照byte劃分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定類型變量的時候經常在特 定的內存地址訪問,這就需要各種類型數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
對齊的作用和原因:各個硬件平臺對存儲空間的處理上有很大的不同。一些平臺對某些特定類型的數據只能從某些特定地址開始存取。比如有些架構的CPU在訪問一個沒有進行對齊的變量的時候會發生錯誤,那么在這種架構下編程必須保證字節對齊.其他平臺可能沒有這種情況,但是最常見的是如果不按照適合其平臺要求對數據存放進行對齊,會在存取效率上帶來損失。比如有些平臺每次讀都是從偶地址開始,如果一個int型(假設為32位系統)如果存放在偶地址開始的地方,那么一個讀周期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要2個讀周期,并對兩次讀出的結果的高低字節進行拼湊才能得到該32bit數據。顯然在讀取效率上下降很多。
結構的存儲分配
編譯器按照結構體成員列表的順序為每個成員分配內存,當存儲成員時需要滿足正確地邊界對齊要求時,成員之間可能出現用于填充地額外內存空間。32位系統每次分配字節數最多為4個字節,64位系統分配字節數最多為8個字節。
以下圖表是在不同系統中基本類型數據內存大小和默認對齊模數:
注:此外指針所占內存的長度由系統決定,在32位系統下為32位(即4個字節),64位系統下則為64位(即8個字節).
沒有#pragma pack宏的對齊
對齊規則:
結構體的起始存儲位置必須是能夠被該結構體中最大的數據類型所整除。
每個數據成員存儲的起始位置是自身大小的整數倍(比如int在32位機為4字節,則int型成員要從4的整數倍地址開始存儲)。
結構體總大?。ㄒ簿褪莝izeof的結果),必須是該結構體成員中最大的對齊模數的整數倍。若不滿足,會根據需要自動填充空缺的字節。
結構體包含另一個結構體成員,則被包含的結構體成員要從其原始結構體內部最大對齊模數的整數倍地址開始存儲。(比如struct a里存有struct b,b里有char,int,double等元素,那b應該從8的整數倍開始存儲。)
結構體包含數組成員,比如char a[3],它的對齊方式和分別寫3個char是一樣的,也就是說它還是按一個字節對齊。如果寫:typedef char Array[3],Array這種類型的對齊方式還是按一個字節對齊,而不是按它的長度3對齊。
結構體包含共用體成員,則該共用體成員要從其原始共用體內部最大對齊模數的整數倍地址開始存儲。
現在給出一個結構體,我們針對win-32和Linux-32進行分析,
例1:
struct MyStruct{ char a; int b; long double c;};
解答:
win-32位系統下:
由上圖可知該結構體的最大對齊模數為sizeof(long double)=8;假設MyStruct從地址空間0x0000開始存放。char為1個字節,所以a存放于0x0000中;int為4個字節,根據規則,b存儲的起始地址必須為其對齊模數4的整數倍,所以a后面自動填充空缺字節空間0x0001-0x0003,因此b存放于0x0004-0x0007中。long double是8個字節,由于32位系統每次最多分配4個字節,則首先分配0x0008-0x000B,由于不夠存儲空間,則繼續分配0x000C-0x000F,所以c存儲在0x0008-0x000F中,由于此時總存儲空間為4+4+8=16;則16滿足最大對齊模數sizeof(long double)=8的整數倍;因此,sizeof(MyStruct)=16個字節。
Linux-32位系統下:
由上圖可知該結構體的最大對齊模數為4;假設MyStruct從地址空間0x0000開始存放。char為1個字節,所以a存放于0x0000中;int為4個字節,根據規則,b存儲的起始地址必須為其對齊模數4的整數倍,所以a后面自動填充空缺字節空間0x0001-0x0003,因此b存放于0x0004-0x0007中。long double是12個字節,由于32位系統每次最多分配4個字節,則首先分配0x0008-0x000B,由于不夠存儲空間,則繼續分配0x000C-0x000F,仍然不滿足存儲c,則繼續分配0x0010-0x0013,所以c存儲在0x0008-0x0013中,由于此時總存儲空間為4+4+12=20;則20滿足最大對齊模數4的整數倍;因此,sizeof(MyStruct)=20個字節。
注:以下的所有例子都是在win-32下實現
例2:
struct B{ char a; int b; char c; };
由上圖可知該結構體的最大對齊模數為sizeof(int)=4;假設B從地址空間0x0000開始存放。char為1個字節,所以a存放于0x0000中;int為4個字節,根據規則,b存儲的起始地址必須為其對齊模數4的整數倍,所以a后面自動填充空缺字節空間0x0001-0x0003,因此b存放于0x0004-0x0007中。c也是char類型,所以c存放在0x0008中;此時結構體B總的大小為4+4+1=9個字節;則9不能滿足最大對齊模數4的整數倍;因此在c的后面自動填充空間0x0009-0x000B,使其滿足最大對齊模數的倍數,最終結構體B的存儲空間為0x0000-0x000B;則sizeof(B)=12個字節。
例3:空結構體
struct C{ };sizeof(C) = 0或sizeof(C);
C為空結構體,在C語言中占0字節,在C++中占1字節。
例4:結構體有靜態成員
struct D{ char a; int b; static double c; //靜態成員 };
靜態成員變量存放在全局數據區內,在編譯的時候已經分配好內存空間,所以對結構體的總內存大小不做任何貢獻;因此,sizeof(D)=4+4=8個字節
例5:結構體中包含結構體
struct E{ int a; double b; float c; }; struct F{ char e[2]; int f; short h; struct E i; };
在結構體E中最大對齊模數是sizeof(double)=8;且sizeof(E)=8+8+8=24個字節;在結構體F中,除了結構體成員E之外,其他的最大對齊模數是sizeof(int)=4;又因為結構體E中最大對齊模數是sizeof(double)=8;所以結構體F的最大對齊模數取E的最大對齊模數8;因此,sizeof(F)=4+4+8+24=40個字節。
例6:結構體包含共用體
union union1 { long a; double b; char name[9]; int c[2]; }; struct E{ int a; double b; float c; union1 MyUnion; };
共用體中的最大對齊模式是sizeof(double)=8;則sizeof(union1)=16;結構體E的最大對齊模數也是8;則sizeof(E)=8+8+8+16=40個字節。
例7:結構體包含指針成員
typedef struct A{ char a; int b; float c; double d; int *p; char *pc; short e; }A;
結構體包含的指針成員的大小根據系統類型決定,由于這里是在win-32位系統下分析,則指針大小為4個字節;因此,結構體A的最大對齊模數為sizeof(double)=8;則sizeof(A)=4+4+8+8+4+4+8=40個字節。
存在#pragma pack宏的對齊
#pragma pack (n) //編譯器將按照n個字節對齊 #pragma pack () //取消自定義字節對齊方式
對齊規則:
結構,聯合,或者類的數據成員,第一個放在偏移為0的地方,以后每個數據成員的對齊,按照#pragma pack指定的數值和自身對齊模數中較小的那個。
例8:按指定的對齊模數
#pragma pack (2) /*指定按2字節對齊*/ struct G{ char b; int a; double d; short c; }; #pragma pack () /*取消指定對齊,恢復缺省對齊*/
在結構體G中成員變量的最大對齊模數是sizeof(double)=8;又因為指定對齊模數是2;所以取其較小者2為結構體G的最大對齊模數;則sizeof(G)=2+4+8+2=16;由于16是2的整數倍,則不需要填充。
總結
在分析結構體字節對齊時,首先確定有沒有利用#pragma pack()宏定義指定對齊模數;根據情況對應上面進行兩種情況分析,針對不同的系統會得到不同的結果。
補充:
在Visual C++下可以用__declspec(align(#))聲明數據按#字節對齊
GUN C下可以使用以下命令:
__attribute__((aligned (n))),讓所作用的結構成員對齊在n字節自然邊界上。如果結構中有成員的長度大于n,則按照最大成員的長度來對齊
__attribute__((__packed__)),取消結構在編譯過程中的優化對齊,按照實際占用字節數進行對齊。
C++11新加關鍵字alignas(n)