一.爲何要字節對齊
簡單來說就是提高cpu對內存的訪問效率。爲了訪問未對齊的內存,處理器需要作兩次內存訪問;然而,對齊的內存訪問僅需要一次訪問。比如有些平臺每次讀都是從偶地址開始,如果一個int型(假設爲32位系統)存放在偶地址開始的地方 ,那麼讀一個週期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要讀2個週期,並對兩次讀出的結果的高低字節進行拼湊才能得到該32bit數據。
二.基本概念
(1)數據類型自身的對齊值:任何基本數據類型T的自身對齊值就是T的大小,即sizeof(T)。char型、short型、int型、long型、float型、double型的自身對齊值分別爲1、2、4、4、4、8,單位爲字節。
(2)結構體或者類的自身對齊值:其成員變量自身對齊值中最大的那個值。
(3)指定對齊值:使用#pragma pack(value)時指定的對齊值。
(4)數據成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。
(5)圓整值:編譯器對結構體進行圓整(即,在結構體最末填充一定的字節)。圓整值大小爲結構體的自身對齊值與#pragma pack(value)中指定的對齊值中較小的那個值。
三.字節對齊規則
有效對齊值N是最終用來決定數據存放地址方式的值。有效對齊值N,就是表示“對齊在N上”,也就是說該數據“存放起始地址%N=0”。而數據結構中數據變量都是按定義的先後順序來排放的。第一個數據變量的起始地址就是數據結構的起始地址。結構體的成員變量要對齊排放,結構體本身也要根據自身的有效對齊值圓整(即結構體成員變量佔用總長度需要對結構體有效對齊值的的整數倍)。
即:
(1)結構體變量的首地址能夠被結構體有效對齊值整除。
(2)結構體每個成員相對於結構體首地址的偏移量(offset)都是該成員有效對齊值的整數倍。如有需要,編譯器會在成員之間加上中間填充字節。
(3)最後對結構體進行圓整操作,結構體總大小爲結構體有效對齊值的整數倍,如有需要,編譯器會在最末一個成員之後加上末尾填充字節來進行圓整。
若出現結構體嵌套,則規則修改如下:
(1)結構體變量的首地址能夠被結構體有效對齊值整除。
(2)結構體每個成員相對於結構體首地址的偏移量(offset)都是該成員有效對齊值的整數倍。如有需要,編譯器會在成員之間加上中間填充字節。
(3)結構體的複合成員相對於結構體首地址的偏移量(offset)都是複合成員有效對齊值的整數倍。如有需要,編譯器會在成員之間加上中間填充字節。
(4)最後對結構體進行圓整操作,結構體總大小爲結構體有效對齊值的整數倍,如有需要,編譯器會在最末一個成員之後加上末尾填充字節來進行圓整。
四.實例分析
<實例一>
- struct B
- {
- char b;
- int a;
- short c;
- };
假 設B從地址空間0x0000開始排放。該例子中沒有定義指定對齊值,在筆者環境下,該值默認爲4。第一個成員變量b的自身對齊值是1,比指定或者默認指定對齊值4小,所以其有效對齊值爲1,所以其存放地址0x0000,符合0x0000%1=0。第二個成員變量a,其自身對齊值爲4,所以有效對齊值也爲4, 所以只能存放在起始地址爲0x0004到0x0007這四個連續的字節空間中,符合0x0004%4=0,且緊靠第一個變量。第三個變量c,自身對齊值爲 2,所以有效對齊值也是2,可以存放在0x0008到0x0009這兩個字節空間中,符合0x0008%2=0。所以從0x0000到0x0009存放的 都是B內容。再看數據結構B的自身對齊值爲其數據成員中最大對齊值(這裏是b)所以就是4,所以結構體的有效對齊值也是4。根據結構體圓整的要求, 0x0009到0x0000=10字節,(10+2)%4=0。所以0x0000A到0x000B也爲結構體B所佔用。故B從0x0000到0x000B 共有12個字節,sizeof(struct B)=12。
其實如果就這一個就來說它已將滿足字節對齊了,因爲它的起始地址是0,因此肯定是對齊的,之所以在後面補充2個字節,是因爲編譯器爲了實現結構數組的存取效率,試想如果我們定義了一個結構B的數組,那麼第一個結構起始地址是0沒有問題,但是第二個結構呢?按照數組的定義,數組中所有元素都是緊挨着的,如果我們不把結構的大小補充爲4的整數倍,那麼下一 個結構的起始地址將是0x0000A,這顯然不能滿足結構的地址對齊了,因此我們要把結構補充成有效對齊大小的整數倍。其實諸如:對於char型數據,其 自身對齊值爲1,對於short型爲2,對於int、float、double類型,其自身對齊值爲4,這些已有類型的自身對齊值也是基於數組考慮的,只是因爲這些類型的長度已知了,所以他們的自身對齊值也就已知了。
<實例二>
- #pragma pack (2) /*指定按2字節對齊*/
- struct C
- {
- char b;
- int a;
- short c;
- };
- #pragma pack () /*取消指定對齊,恢復缺省對齊*/
第 一個變量b的自身對齊值爲1,指定對齊值爲2,所以,其有效對齊值爲1,假設C從0x0000開始,那麼b存放在0x0000,符合0x0000%1= 0;第二個變量,自身對齊值爲4,指定對齊值爲2,所以有效對齊值爲2,所以順序存放在0x0002、0x0003、0x0004、0x0005四個連續 字節中,符合0x0002%2=0。第三個變量c的自身對齊值爲2,所以有效對齊值爲2,順序存放在0x0006、0x0007中,符合 0x0006%2=0。所以從0x0000到0x00007共八字節存放的是C的變量。又結構體C的自身對齊值爲4,所以C的有效對齊值爲2。又8%2=0,C 只佔用0x0000到0x0007的八個字節。所以sizeof(struct C)=8.
五.如何修改編譯器的默認對齊值?
1.在VC IDE中,可以這樣修改:[Project]|[Settings],c/c++選項卡Category的Code Generation選項的Struct Member Alignment中修改,默認是8字節。
2.在編碼時,可以這樣動態修改:#pragma pack (vaule).注意:是pragma而不是progma.
(2)結構體每個成員相對於結構體首地址的偏移量(offset)都是該成員有效對齊值的整數倍。如有需要,編譯器會在成員之間加上中間填充字節。
(3)結構體中的複合成員相對於結構體首地址的偏移量(offset)是該複合成員有效對齊值的整數倍。如有需要,編譯器會在成員之間加上中間填充字節。
(3)最後對結構體進行圓整操作,結構體總大小爲結構體有效對齊值(其成員變量自身對齊值以及複合成員結構體中的自身對齊值中最大的那個值)的整數倍,如有需要,編譯器會在最末一個成員之後加上末尾填充字節來進行圓整,。