詳解4字節對齊


所謂的字節對齊,就是各種類型的數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這個就是對齊。我們經常聽說的對齊在N上,它的含義就是數據的存放起始地址%N==0。具體對齊規則會在下面的篇幅中介紹。首先還是讓我們來看一下,為什么要進行字節對齊吧。
 
各個硬件平台對存儲空間的處理上有很大的不同。一些平台對某些特定類型的數據只能從某些特定地址開始存取。比如有些架構的CPU,諸如SPARC在訪問一個沒有進行對齊的變量的時候會發生錯誤,那么在這種架構上必須編程必須保證字節對齊。而有些平台對於沒有進行對齊的數據進行存取時會產生效率的下降。讓我們來以x86為例看一下如果在不進行對齊的情況下,會帶來什么樣子的效率低下問題,看下面的數據結構聲明:


view plaincopy to clipboardprint?
01.struct A {  
02.    char c;  
03.    int i;  
04.};  
05.struct A a;  
 


Code 13-1
 
假設變量a存放在內存中的起始地址為0x00,那么其成員變量c的起始地址為0x00,成員變量i的起始地址為0x01,變量a一共占用了5個字節。當CPU要對成員變量c進行訪問時,只需要一個讀周期即可。而如若要對成員變量i進行訪問,那么情況就變得有點復雜了,首先CPU用了一個讀周期,從0x00處讀取了4個字節(注意由於是32位架構),然后將0x01-0x03的3個字節暫存,接着又花費了一個讀周期讀取了從0x04-0x07的4字節數據,將0x04這個字節與剛剛暫存的3個字節進行拼接從而讀取到成員變量i的值。為了讀取這個成員變量i,CPU花費了整整2個讀周期。試想一下,如果數據成員i的起始地址被放在了0x04處,那么讀取其所花費的周期就變成了1,顯然引入字節對齊可以避免讀取效率的下降,但這同時也浪費了3個字節的空間(0x01-0x03)。
 
有了上述的基本概念之后,讓我們來看一下,編譯器是按照什么樣的原則進行對齊的。首先有3個重要的概念:自身對齊值,指定對齊值和有效對齊值。
 
自身對齊值:即數據類型的自身的對齊值。例如char型的數據,其自身對齊值為1字節;short型的數據,其自身對齊值為2字節;int,float,long類型,其自身對齊值為4字節;double類型,其自身對齊值為4字節;而struct和class類型的數據其自身對齊值為其成員變量中自身對齊值最大的那個值。
 
指定對齊值:#pragma pack (value)時指定的對齊值value
 
有效對齊值:上述兩個對齊值中最小的那個。
 
我們一般說的對齊在N上,都是指有效對齊在N上。說了這么多,還是讓我們先來看一些例子來加深對這些概念的理解吧。例:假設在x86機器上,假設編譯器按默認4字節進行對齊

 


view plaincopy to clipboardprint?
01.struct A {  
02.    char c;  
03.    int i;  
04.    short s;  
05.};  
06.  
07.#pragma pack (2)   /* 指定按2字節對齊 */  
08.struct B {  
09.    char c;  
10.    short s;  
11.    int i;  
12.};  
13.#pragma pack ()    /* 恢復默認對齊 */  
 


Code 13-2
 
讓我們來考慮一下sizeof(struct A)和sizeof(struct B)的結果各應該是什么。首先來看sizeof(struct A),假設A的起始地址為0x00,做這樣的假設只是為了更方便理解,其實A始終被放在對齊邊界上,這並不影響sizeof的結果,在接下來的例子中,我們也會繼續沿用這個假設。言歸正傳,數據成員c的自身對齊值=1,指定對齊值=4(默認),所以其有效對齊值為1,因0x00%1==0,所以它被存放在0x00處;數據成員i的自身對齊值=4,指定對齊值=4,可得出其有效對齊值為4,因0x01%4 != 0,因此它應該被存放在0x04地址處,占用0x05,0x06,0x07共4個字節;接下來看數據成員s的自身對齊值=2,指定對齊值=4,得出有效對齊值為2,因0x08%2 == 0,因此它被存放在起始地址為0x08處,並占用2字節;最后再看數據結構A自身的對齊值=4(最大數據成員自身對齊值),指定對齊值=4,得有效對齊值為4,因0x0A%4 != 0,因此多占用0x0A和0x0B為結構體A所用(這一步的作用是基於結構體數組的出發,對於結構體或者類,要將它們補充成其有效對齊值的整數倍,這點請千萬注意)。由此可見sizeof(struct A)的結果應該是=1+3(空閑空間)+4+2+2(結構體補充)=12字節。
 
接下來讓我們考察sizeof(struct B)的結果。這里需要注意的是,在B被聲明前,指定對齊值已經被設置為2個字節。數據成員c的有效對齊值為1,存放起址0x00,s的有效對齊值為2,存放起址0x02,i的有效對齊值也為2,存放起址為0x04,累加起來一共是8個字節,已經是數據結構B的有效對齊值2的整數倍了。因此sizeof(struct B)的結果8個字節。
 
看到這里,應該對字節對齊有了一定的了解了吧。接下來我們要看一個更加復雜的例子:假設在x86機器上,假設編譯器按默認4字節進行對齊 

 


view plaincopy to clipboardprint?
01.#pragma pack(8)  
02.struct S1 {  
03.    char a;  
04.    long b;  
05.};  
06.  
07.struct S2 {  
08.    char c;  
09.    struct S1 d;  
10.    long long e;  
11.};  
12.#pragma pack()  
13.   
 


Code 13-3
 
運用上面所學到的知識,應該不難得出sizeof(struct S1)的值為8字節,其中a的有效對齊值為1,b的有效對齊值為4,結構S1的有效對齊值為4。現在讓我們來看看sizeof(struct S2)的值會是多少呢?首先成員c的有效對齊值為1,S1的自身對齊值為成員的最大自身對齊值,即4字節,其指定對齊值為8,則其有效對齊值也為4,存放起址應該為0x04,並且占用8個字節(0x04+0x08=0x0C),其中0x01-0x03被用來填充。接下去數據成員e的有效對齊值為4,存放起址應該是0x0D % 8 == 0,占用8個字節(0x10)。最后考察S2本身的有效對齊值應該是4字節,而0x0D%8==0,就不需要填充了,因此sizeof(struct S2)=20。
 
在vc6工具中,我們可以選擇[project]->[Settings]->[C/C++]->[Code Generation]->[Struct member alignment]來更改默認對齊字節數。
 
參考:http://www.sco.com/developers/devspecs/abi386-4.pdf


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM