結構體邊界對齊
許多實際的計算機系統對基本型別資料在記憶體中存放的位置有限制,它們會要求這些資料的首位址的值是某個數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
2. #pragma pack(8)
3. struct example1
4. ;
8. struct example2
9. ;
14. #pragma pack()
15. int main(int argc, char* argv)
16.
問程式的輸入結果是什麼?
答案是:816
4不明白?還是不明白?下面一一道來:
2.1 自然對界
struct是一種復合資料型別,其構成元素既可以是基本資料型別(如int、long、float等)的變數,也可以是一些復合資料型別(如 array、struct、union等)的資料單元。對於結構體,編譯器會自動進行成員變數的對齊,以提高運算效率。預設情況下,編譯器為結構體的每個 成員按其自然對界(natural alignment)條件分配空間。各個成員按照它們被宣告的順序在記憶體中順序儲存,第乙個成員的位址和整個結構的位址相同。
自然對界(natural alignment)即預設對齊方式,是指按結構體的成員中size最大的成員對齊。
例如:struct naturalalign
;在上述結構體中,size最大的是short,其長度為2位元組,因而結構體中的char成員a、c都以2為單位對齊,sizeof(naturalalign)的結果等於6;
如果改為:
struct naturalalign
;其結果顯然為12。
2.2指定對界
一般地,可以通過下面的方法來改變預設的對界條件:
· 使用偽指令#pragma pack (n),編譯器將按照n個位元組對齊;
· 使用偽指令#pragma pack (),取消自定義位元組對齊方式。
注意:如果#pragma pack (n)中指定的n大於結構體中最大成員的size,則其不起作用,結構體仍然按照size最大的成員進行對界。
例如:#pragma pack (n)
struct naturalalign
;#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問題
預設情況下,在32位cpu裡,gcc對於結構體的對齊方式是按照四個位元組來對齊的。看以下結構體
typedef struct packpack;
對於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編譯選項了。
字的邊界對齊問題
arm微處理器中支援位元組 半字 字三種資料型別,其中,字需要4位元組對齊 位址的低兩位為0 半字需要2位元組對齊 位址的最低位為0 1 字對齊資料,也就是說每個資料都是用字 32位 來表示的,而arm中的儲存單元都是以位元組為單位,那麼要索引乙個資料,需要連續的4個位元組才行,比如,0x0000 ...
整數邊界對齊方式 c中結構體邊界對齊
原則1 普通資料成員對齊規則 第乙個資料成員放在offset為0的地方,以後每個資料成員儲存的起始位置要從該成員大小的整數倍開始 比如int在32位機為 位元組,則要從4的整數倍位址開始儲存 原則2 結構體成員對齊規則 如果乙個結構裡有某些結構體成員,則該結構體成員要從其內部最大元素大小的整數倍位址...
成員邊界對齊 pragma pack n
intel 微軟等公司曾經出過一道類似的面試題 1.include 2.pragma pack 8 3.struct example1 4.8.struct example2 9.14.pragma pack 15.int main int argc,char argv 16.問程式的輸入結果是什麼...