結構體邊界對齊
許多實際的計算機系統對基本類型數據在內存中存放的位置有限制,它們會要求這些數據的首地址的值是某個數k(通常它爲4或8)的倍數,這就是所謂的內存對齊,而這個k則被稱爲該數據類型的對齊模數(alignment modulus)。當一種類型S的對齊模數與另一種類型T的對齊模數的比值是大於1的整數,我們就稱類型S的對齊要求比T強(嚴格),而稱T比S弱(寬鬆)。這種強制的要求一來簡化了處理器與內存之間傳輸系統的設計,二來可以提升讀取數據的速度。比如這麼一種處理器,它每次讀寫內存的時候都從某個8倍數的地址開始,一次讀出或寫入8個字節的數據,假如軟件能保證double類型的數據都從8倍數地址開始,那麼讀或寫一個double類型數據就只需要一次內存操作。否則,我們就可能需要兩次內存操作才能完成這個動作,因爲數據或許恰好橫跨在兩個符合對齊要求的8字節內存塊上。某些處理器在數據不滿足對齊要求的情況下可能會出錯,但是Intel的IA32架構的處理器則不管數據是否對齊都能正確工作。不過Intel奉勸大家,如果想提升性能,那麼所有的程序數據都應該儘可能地對齊。
規則:
第一,編譯器按照成員列表的順序給每個成員分配內存.
第二,當成員需要滿足正確的邊界對齊時,成員之間用額外字節填充.
第三,結構體的首地址必須滿足結構體中邊界要求最爲嚴格的數據類型所要求的地址.
第四,結構體的大小爲其最寬基本類型的整數倍.
sizeof操作符能夠得出一個結構體的整體長度,包括因邊界對齊而額外填充的那些字節.
offsetof(type, member)宏能求得成員在結構體內的偏移,返回size_t.
關於struct的邊界對齊問題
Intel、微軟等公司曾經出過一道類似的面試題:
1. #include <iostream.h>
2. #pragma pack(8)
3. struct example1
4. {
5. short a;
6. long b;
7. };
8. struct example2
9. {
10. char c;
11. example1 struct1;
12. short e;
13. };
14. #pragma pack()
15. int main(int argc, char* argv[])
16. {
17. example2 struct2;
18. cout << sizeof(example1) << endl;
19. cout << sizeof(example2) << endl;
20. cout << (unsigned int)(&struct2.struct1) - (unsigned int)(&struct2) << endl;
21. return 0;
22. }
問程序的輸入結果是什麼?
答案是:
8
16
4
不明白?還是不明白?下面一一道來:
2.1 自然對界
struct是一種複合數據類型,其構成元素既可以是基本數據類型(如int、long、float等)的變量,也可以是一些複合數據類型(如 array、struct、union等)的數據單元。對於結構體,編譯器會自動進行成員變量的對齊,以提高運算效率。缺省情況下,編譯器爲結構體的每個 成員按其自然對界(natural alignment)條件分配空間。各個成員按照它們被聲明的順序在內存中順序存儲,第一個成員的地址和整個結構的地址相同。
自然對界(natural alignment)即默認對齊方式,是指按結構體的成員中size最大的成員對齊。
例如:
struct naturalalign
{
char a;
short b;
char c;
};
在上述結構體中,size最大的是short,其長度爲2字節,因而結構體中的char成員a、c都以2爲單位對齊,sizeof(naturalalign)的結果等於6;
如果改爲:
struct naturalalign
{
char a;
int b;
char c;
};
其結果顯然爲12。
2.2指定對界
一般地,可以通過下面的方法來改變缺省的對界條件:
· 使用僞指令#pragma pack (n),編譯器將按照n個字節對齊;
· 使用僞指令#pragma pack (),取消自定義字節對齊方式。
注意:如果#pragma pack (n)中指定的n大於結構體中最大成員的size,則其不起作用,結構體仍然按照size最大的成員進行對界。
例如:
#pragma pack (n)
struct naturalalign
{
char a;
int b;
char c;
};
#pragma pack ()
當n爲4、8、16時,其對齊方式均一樣,sizeof(naturalalign)的結果都等於12。而當n爲2時,其發揮了作用,使得sizeof(naturalalign)的結果爲8。
在VC++ 6.0編譯器中,我們可以指定其對界方式,其操作方式爲依次選擇projetct > setting > C/C++菜單,在struct member alignment中指定你要的對界方式。
另外,通過__attribute((aligned (n)))也可以讓所作用的結構體成員對齊在n字節邊界上,但是它較少被使用,因而不作詳細講解。
2.3 面試題的解答
至此,我們可以對Intel、微軟的面試題進行全面的解答。
程序中第2行#pragma pack (8)雖然指定了對界爲8,但是由於struct example1中的成員最大size爲4(long變量size爲4),故struct example1仍然按4字節對界,struct example1的size爲8,即第18行的輸出結果;
struct example2中包含了struct example1,其本身包含的簡單數據成員的最大size爲2(short變量e),但是因爲其包含了struct example1,而struct example1中的最大成員size爲4,struct example2也應以4對界,因爲8大於4,所以#pragma pack (8)中指定的對界對struct example2也不起作用,故19行的輸出結果爲16;
由於struct example2中的成員以4爲單位對界,故其char變量c後應補充3個空,其後纔是成員struct1的內存空間,20行的輸出結果爲4。
補充:上面說的還不是很清楚,總結一下:在默認情況下,VC規定各個成員變量存放的起始地址相對於結構的起始地址的偏移量必須爲該類型所佔字節數的 倍數。 同時爲了確保結構的大小爲字節邊界數(結構中佔用最大空間 的類型所佔用的字節數)的倍數,所以在爲最後一個成員變量申請空間後,還會根據需要自動填充空缺的字節。(supermonkey注: 其實這裏相當於把example1的成員展開到example2中來判斷!)
ARM內存邊界對齊以及sizeof問題 |
[ 2011-7-14 5:28:00 | By: 五穀道場 ]
|
默認情況下,在32位cpu裏,gcc對於結構體的對齊方式是按照四個字節來對齊的。看以下結構體 typedef struct pack{ char a; int b; short c; }pack; 對於Pack結構體,默認情況下在arm/386平臺下(別的平臺沒試過)sizeof(pack)=12,求解過程如下: sizeof(char)=1; 下一個int b,由於是四個字節,要求b的開始地址從32的整數倍開始,故需要在a後面填充3個沒用的字節,記爲dump(3),sizeof(b)=4,此時相當於結構體擴充爲 char a; char dump(3); int b; 看short c,現在c的前面有8個字節,c是兩個字節,c的開始地址是從16的整數開始,在b前面不需再加東西.此時對於結構體來說,sizeof(pack)=10,但是這不是最終結果,最後總的字節數也要能被4個字節整除,所以還需在short c後面再加 dump(2); 故總的字節數爲12. 當然以上說的只是簡單的情況,下面談談arm,x86在gcc裏關於內存邊界字節對齊的區別.對於同樣的結構體,在386下 #prama pack(1) 後,sizeof(pack)=1 4 2=7 而在arm下同樣的操作sizeof(pack)=1 4 2 1=8,即雖然b根a之間不要填充但總的長度必須要是4的整數倍. 在arm 下要使結構體按指定字節對齊,可行的方法 1.在makefile里加-fpack-struct 選項,這樣的話對所有的結構按一字節對齊. 不得不說,確實有那麼些質量較差的程序可能需要你部分自然對齊,部分一字 節對齊,此時 2. typedef struct pack{ }__attribute__((packed)) 可利用__attribute__屬性 當然最後的方式,還是自己去看arm體系結構與gcc編譯選項了。 |