所謂的字節對齊,就是各種類型的數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這個就是對齊。我們經常聽說的對齊在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