一,什么是內存對齊?內存對齊用來做什么?
所謂內存對齊,是為了讓內存存取更有效率而采用的一種編譯階段優化內存存取的手段。
比如對於int x;(這里假設sizeof(int)==4),因為cpu對內存的讀取操作是對齊的,如果x的地址不是4的倍數,那么讀取這個x,需要讀取兩次共8個字節,然后還要將其拼接成一個int,這比存取對齊過的x要麻煩很多。
二,怎么算內存對齊大小(理論)?
對於簡單類型,如int,char,float等,其對齊大小為其本身大小,即align(int) == sizeof(int),align(char)==sizeof(char),等等。
對於復合類型,如struct,class,其本身並無所謂對齊,因為CPU沒有直接存取一個struct的指令。對於struct而言,它的對齊指的是它里面的所有成員變量都是對齊的,class同理。
下面就講講struct對齊是怎么回事。
首先要明白三個點:
1,內存對齊是指首地址對齊,而不是說每個變量大小對齊;
2,結構體內存對齊要求結構體內每一個成員變量都是內存對齊的;
3,結構體對齊除了第2點之外還要求結構體數組也必須是對齊的,也就是說每個相鄰的結構體內部都是對齊的。
OK,先知道上面這3點之后,開始接觸怎么算對齊大小。
程序員可自己指定某些數據的對齊大小,通過使用下面的預處理指令,指定對齊大小為x。(這里需要注意:只能指定2的n次方作為對齊大小,對於指定對齊大小為6,9,10這樣的編譯器可能會不予理會)
#pragma pack(x) //... #pragma pack()
那到現在,可能大家有個疑問了,那對於int(這里假設sizeof(int)==4),手動指定對齊大小為8,那align(int)是等於sizeof(int)還是等於8呢 ?
這里大家可以記住,align(x) = min ( sizeof(x) , packalign) , 即sizeof(x)和指定對齊大小哪個小,對齊大小就為哪個。
因此,上面的疑問答案是align(int)=sizeof(int)=4 。
三,怎么算內存對齊大小(示范)?
#include <cassert> int main(int argc, char* argv[]) { //此處指定對齊大小為1 //對於a,實際對齊大小為min(sizeof(int),1)=min(4,1)=1 //對於b,實際對齊大小為min(sizeof(char),1)=min(1,1)=1 //編譯器會確保TEST_A首地址即a的地首址是1字節對齊的,此時a對齊 //對於b,由於b要求首地址1字節對齊,這顯然對於任何地址都合適,所以a,b都是對齊的 //對於TEST_A數組,第一個TEST_A是對齊的(假設其地址為0),則第二個TEST_A的首地址為(0+5=5),對於第二個TEST_A的兩個變量a,b均對齊 //OK,對齊合理。因此整個結構體的大小為5 #pragma pack(1) struct TEST_A { int a; char b; }; #pragma pack() assert(sizeof(TEST_A) == 5); //此處指定對齊大小為2 //對於a,實際對齊大小為min(sizeof(int),2)=min(4,2)=2 //對於b,實際對齊大小為min(sizeof(char),2)=min(1,2)=1 //編譯器會確保TEST_A首地址即a的地首址是2字節對齊的,此時a對齊 //對於b,由於b要求首地址1字節對齊,這顯然對於任何地址都合適,所以a,b都是對齊的 //對於TEST_B數組,第一個TEST_B是對齊的(假設其地址為0),則第二個TEST_B的首地址為(0+5=5),對於第二個TEST_B的變量a,顯然地址5是不對齊於2字節的 //因此,需要在TEST_B的變量b后面填充1字節,此時連續相連的TEST_B數組才會對齊 //OK,對齊合理。因此整個結構體的大小為5+1=6 #pragma pack(2) struct TEST_B { int a; char b; }; #pragma pack() assert(sizeof(TEST_B) == 6); //此處指定對齊大小為4 //對於a,實際對齊大小為min(sizeof(int),2)=min(4,4)=4 //對於b,實際對齊大小為min(sizeof(char),2)=min(1,4)=1 //編譯器會確保TEST_A首地址即a的地首址是4字節對齊的,此時a對齊 //對於b,由於b要求首地址1字節對齊,這顯然對於任何地址都合適,所以a,b都是對齊的 //對於TEST_C數組,第一個TEST_C是對齊的(假設其地址為0),則第二個TEST_C的首地址為(0+5=5),對於第二個TEST_C的變量a,顯然地址5是不對齊於4字節的 //因此,需要在TEST_C的變量b后面填充3字節,此時連續相連的TEST_C數組才會對齊 //OK,對齊合理。因此整個結構體的大小為5+3=8 #pragma pack(4) struct TEST_C { int a; char b; }; #pragma pack() assert(sizeof(TEST_C) == 8); //此處指定對齊大小為8 //對於a,實際對齊大小為min(sizeof(int),8)=min(4,8)=4 //對於b,實際對齊大小為min(sizeof(char),8)=min(1,8)=1 //編譯器會確保TEST_A首地址即a的地首址是4字節對齊的,此時a對齊 //對於b,由於b要求首地址1字節對齊,這顯然對於任何地址都合適,所以a,b都是對齊的 //對於TEST_D數組,第一個TEST_D是對齊的(假設其地址為0),則第二個TEST_D的首地址為(0+5=5),對於第二個TEST_D的變量a,顯然地址5是不對齊於4字節的 //因此,需要在TEST_D的變量b后面填充3字節,此時連續相連的TEST_D數組才會對齊 //OK,對齊合理。因此整個結構體的大小為5+3=8 #pragma pack(8) struct TEST_D { int a; char b; }; #pragma pack() assert(sizeof(TEST_D) == 8); //此處指定對齊大小為8 //對於a,實際對齊大小為min(sizeof(int),8)=min(4,8)=4 //對於b,實際對齊大小為min(sizeof(char),8)=min(1,8)=1 //對於c,這是一個數組,數組的對齊大小與其單元一致,因而align(c)=align(double)=min(sizeof(double),8)=min(8,8)=8 //對於d,實際對齊大小為min(sizeof(char),8)=min(1,8)=1 //編譯器會確保TEST_A首地址即a的地首址是4字節對齊的,此時a對齊 //對於b,由於b要求首地址1字節對齊,這顯然對於任何地址都合適,所以a,b都是對齊的 //對於c,由於c要求首地址8字節對齊,因此前面的a+b=5,還要在c后面補上3個字節才能對齊 //對於d,顯而易見,任何地址均對齊,此時結構體大小為4+1+3+10*8+1=89 //對於TEST_E數組,第一個TEST_E是對齊的(假設其地址為0),則第二個TEST_E的首地址為(0+89=89),對於第二個TEST_E的變量a,顯然地址89是不對齊於4字節的 //因此,需要在TEST_E的變量d后面填充7字節,此時連續相連的TEST_E數組才會對齊 //(注意:此處不僅要確保下一個TEST_E的a,b變量對齊,還要確保c也對齊,所以這里不是填充3字節,而是填充7字節) //OK,對齊合理。因此整個結構體的大小為(4)+(1+3)+(10*8)+(1+7)=96 #pragma pack(8) struct TEST_E { int a; char b; double c[10]; char d; }; #pragma pack() assert(sizeof(TEST_E) == 96); return 0; }
四,內存對齊相關
使用msvc未公開編譯選項可以查看c++類的內存布局。使用方法:啟動vs命令行,輸入cl 【source.cpp】 /d1reportSingleClassLayout【CBaseClass1】以查看單個class的內存布局,輸入cl 【source.cpp】 /d1reportAllClassLayout以查看所有類的內存布局。注意:/d1reportSingleClassLayout【CBaseClass1】沒有空格 !!
大家可以用這個來對照我上面講的例子來看編譯器是怎么安排對齊的。
這個東東是神器,類似於宏展開時的選項(輸出與處理過之后的源文件),一切內部布局方面的真相全都展現在你眼前,包括坑腦細胞的虛函數、虛函數表、虛基類表、虛繼承等一系列坑爹。
五,參考資料:
1,http://blog.csdn.net/arethe/article/details/2548867
2,http://msdn.microsoft.com/en-us/library/83ythb65.aspx
3,http://msdn.microsoft.com/en-us/library/9dbwhz68.aspx
