問題引入
定義一個結構體的一般形式為:
struct 結構體名 { //類型說明符 成員名; };
例如有如下結構體:
struct Stu { int id; char sex; float hight; };
那么一個這樣的結構體變量占多大內存呢?也就是
cout<<sizeof(Stu)<<endl; 會輸出什么?
在了解字節對齊方式之前想當然的會以為:sizeof(Stu) = sizeof(int)+sizeof(char)+sizeof(float) = 9.
然而事實並非如此!
字節對齊原則
在系統默認的對齊方式下:每個成員相對於這個結構體變量地址的偏移量正好是該成員類型所占字節的整數倍,且最終占用字節數為成員類型中最大占用字節數的整數倍。
在這個例子中,id的偏移量為0(0=4*0),sex的偏移量為4(4=1*4),hight的偏移量為8(8=2*4),此時占用12字節,也同時滿足12=3*4.所以sizeof(Stu)=12.
(1)出現繼承關系時:
struct A{ int a; char b; }; struct B:A{ char c; int d; long long e; };
基類的成員總是在派生類的前面。而且即使有字節對齊,基類對齊后派生類的成員不會占用基類填充的字節,即計算好基類所占字節數后,這些字節只能由基類擁有,不能被派生類的成員占用(即char b后面有3字節的填充,之后才有char c)在派生類中成員的分布只需滿足每個變量起始字節序號為該類型所占字節數的整數倍且最終大小為占用字節數最大的類型對應的字節數的整數倍。排列如下:
/* 0 4 8 12 16 24 | | | | | | aaaab---c---ddddeeeeeeee */
(2)出現關聯關系時:
必須滿足:1.結構體/類與結構體/類之間不會共用自動補齊的內存,即一個結構體變量/對象對齊之后填補的內存不允許被其他變量/對象占用;2.結構體的起始字節位置必須是該結構體中所占字節數最大的變量的字節數的整數倍;3.最終所占字節數必須是最大所占字節數最大的變量的字節數的整數倍。
(3)出現強制對齊方式時:
當然,有時候考慮到其他特殊用途,使用#pragma pack(n)來設定以n字節對齊的方式(n可取2的較小次冪,即1,2,4,8,具體取值范圍以及默認值與所使用的編譯器有關。筆者所測試的環境下vs/vc默認為8,gcc默認為4)。
n字節對齊就是說變量存放的起始地址的偏移量有兩種情況:
第一、如果n大於等於該變量所占用的字節數,那么偏移量必須滿足默認的對齊方式;
第二、如果n小於該變量的類型所占用的字節數,那么偏移量為n的倍數,不用滿足默認的對齊方式。
結構的總大小也有個約束條件,分下面兩種情況:如果n大於所有成員變量類型所占用的字節數,那么結構的總大小必須為占用空間最大的變量占用的空間數的倍數;否則必須為n的倍數。
比如有以下代碼:
1 #pragma pack(push) //保存對齊狀態 2 #pragma pack(4)//設定為4字節對齊 3 struct Test 4 { 5 char c1; 6 double d; 7 int i; 8 char c2; 9 }; 10 #pragma pack(pop)//恢復對齊狀態
這時候編譯器就會被迫使用我們約定的字節對齊方式,即4字節對齊,因此c1占4字節,d占8字節,i占4字節,c2占4字節,共20字節;
如果我們沒有設置字節對齊方式,仍然使用默認對齊的話,這里sizeof(Test) = 24。
注:以上測試效果及完整代碼見github.
最后一個問題,為什么要進行字節對齊?
《Windows核心編程》里這樣說:當CPU訪問正確對齊的數據時,它的運行效率最高,當數據大小的數據模數的內存地址是0時,數據是對齊的。例如:WORD值應該是總是從被2除盡的地址開始,而DWORD值應該總是從被4除盡的地址開始,數據對齊不是內存結構的一部分,而是CPU結構的一部分。當CPU試圖讀取的數值沒有正確的對齊時,CPU可以執行兩種操作之一:產生一個異常條件;執行多次對齊的內存訪問,以便讀取完整的未對齊數據,若多次執行內存訪問,應用程序的運行速度就會慢。
補充:C++類中的數據成員也遵循以上對齊原則,也就是說:
1.在不考慮(或者說在沒有)虛函數和虛繼承的情況下,sizeof(自定義類)也按照類似上面的方式來計算。
2.如果一個類擁有虛函數或者虛繼承,則在數據成員的基礎上相當於多一個指針類型的數據成員(位置在所有數據成員的前面),最后計算時加上即可。
3.如果一個類或者結構體不含有任何數據成員,且無虛函數以及虛繼承,則sizeof()結果為1。
4.靜態成員不在計算范圍。