文章目錄
- 閱讀指引:
- 範例程式碼
- 為什麼要位元組對齊
- 編譯器對位元組對齊的一些規則
- 結合編譯器原則分析樣本
- 總結
- 1.範例程式碼
- 2.為什麼要位元組對齊
- 3.編譯器對位元組對齊的一些規則
- 4. 結合編譯器分析樣本
- 總結
閱讀指引:
- 範例程式碼
- 為什麼要位元組對齊
- 編譯器對位元組對齊的一些規則
- 結合編譯器原則分析樣本
- 總結
1.範例程式碼
先看一下這段程式的運行結果。
範例程式碼
struct A
{
int a;
char b;
short c;
};
struct B
{
char a;
int b;
short c;
};
#pragma pack(2)
struct C
{
char a;
int b;
short c;
};
#pragma pack(1)
struct D
{
int a;
char b;
short c;
};
int _tmain(int argc, _TCHAR* argv[])
{
cout << sizeof(A) << " "<< sizeof B << " "<< sizeof C << " "<< sizeof D <<endl;
return 0;
} 運行結果如下:8 12 8 7
理論上來說,結構體A與B的大小應該都是一樣的,造成這種原因的就是位元組對齊引起來的。
2.為什麼要位元組對齊 為什麼呢?簡單點說:為了提高存取效率。位元組是記憶體空間分配的最小單位, 在程式中,我們定義的變數可以放在任何位置。其實不同架構 的CPU在訪問特定類型變數時是有規律的,比如有的CPU訪問int型變數時,會從偶數地址開始讀取的,int類型佔用4個位元組(windows平台)。 0X0000,0X0004,0X0008.....這樣只需要讀一次就可以讀出Int類型變數的值。相反地,則需要讀取二次,再把高低位元組相拼才能得到 int類型的值,這樣子看的話,存取效率當然提高了。 通常寫程式的時候,不需要考慮這些情況,編譯都會為我們考慮這些情況,除非針對那些特別架構的 CPU編程的時候的則需要考慮 。當然使用者也可以手工控制對齊。 3.編譯器對位元組對齊的一些規則
我從下面三條說明了編譯器對位元組處理的一些原則。當然除了一些特殊的編譯器在處理位元組對齊的方式也不一樣, 這些情況我未碰到過,就不作說明了。
a. 關於資料類型自身的對齊值,不同類型會按不同的位元組來對齊。
| 類型 |
對齊值(位元組) |
| char |
1 |
| short |
2 |
| int |
4 |
| float |
4 |
| double |
4 |
b. 類、結構體的自身對齊位元組值。對於結構體類型與類對象的對齊原則:使用成員當中最大的對齊位元組來對齊。比如在Struct A中,int a的對齊位元組為4,比char,short都大,所以A的對齊位元組為4 c. 指定對齊位元組值。意思是指使用了宏 #pragma pack(n)來指定的對齊值
d. 類、結構及成員的有效對齊位元組值。有效對齊值=min(類/結構體/成員的自身對齊位元組值,指定對齊位元組值)。 有效對齊值決定了資料的存放方 式,sizeof 運算子就是根據有效對齊值來導出成員大小的。簡單來說, 有效對齊其實就是要求資料成員存放的地址值能被有效對齊值整除,即:地址值%有效對齊值=0
4. 結合編譯器分析樣本
根據上面的原則,分析Struct A的size。結構體的成員記憶體配置是按照定義的順序來分析的。struct A
{
int a;
char b;
short c;
} 為了簡單起見, 我假設Struct A存取的起始地址為 0x0000 在沒有指定對齊值的情況下,分析步驟: step 1: 根據第二條,首先為結構體選擇對齊值:選擇成員中最大的對齊值,即int a,對齊值為4
step 2: 再根據第四條原則,決定有效對齊值:即然沒有手工指定對齊值,則使用預設的值:4(windows 32平台)
step 3: int a 的有效地址值=min(4,4),(因為0x0000%4=0),這樣a的地址就是從 0X0000~0x0003
step 4: char b 的有效對齊值=min(1,4),地址依次從0x0004 (因為Ox0004%1=0)開始,分配一個位元組,位址區段分配情況就是:0x0000~0x0004
step 5: short c 的有效對齊值=min(2,4),理論上說,分配的地址應該是連續的(從0x0005~0x00006),但是由於要求考慮到對齊的情況,所求要求位址區段 位移,這樣就從0x0006(Offset+1,因為0x0006%2=0)開始,分配2個位元組的地址0x0006~0x0007.
目前為止,位址區段的分配情況就是:0x0000~0x0007這樣sizeof(A)的大小=0x0000~0x0007共8個位元組大小,同時,8%4=0保證了Struct A的位址區段與4成偶數倍。
接下來分析Struct B的大小,同樣假設Struct B的起始地址為0x0000,分析步驟如下:
struct B
{
char a;
int b;
short c;
} step 1: 確實結構體B對齊值:選擇成員中最大的對齊值,即int a,對齊值為4
step 2: 確定手工指定對齊值,使用預設的值:4(windows 32, VC6.0平台)
step 3: char a 的有效地址值=min(1,4),a的地址就是 0X0000(因為0x0000%1=0)
step 4: int b 的有效對齊值=min(4,4),地址依次從0x0004~0x0007 (因為Ox0004%1=0)開始,分配4個位元組,目前j位址區段分配情況就是:0x0000~0x0007
step 5: short c 的有效對齊值=min(2,4),c從0x0008~0x0009(因為0x0008%2=0)開始,位移2個位元組的地址0x0006~0x0007.
至止,位址區段的分配情況就是:0x0000~0x0009共10個位元組,但是Struct B的對齊值為4,這就要求地址地段再位移2個位元組,這樣就是從0x0000~0x000B共12(因為12%4=0)個位元組大小。這樣,sizeof(B)=12
再來使用Pragma手工更改了位元組對齊值的情況,先看看Struct C的定義:
#pragma pack(2)
struct C
{
char a;
int b;
short c;
};
在代碼中,手工指定了對齊值為2個位元組,分析步驟如下:
step 1: 確定結構體C對齊值:選擇成員中最大的對齊值,即int a,對齊值為4
step 2: 確定手工指定對齊值,使用手工指定的值:2
step 3: char a 的有效地址值=min(1,2),(因為0x0000%2=0),這樣a的地址就是0x0000
step 4: int b 的有效對齊值=min(4,2),地址依次從0x0002~0x0005 (因為Ox0002%2=0)開始,分配4個位元組,目前位址區段分配情況就是:0x0000~0x0005
step 5: short c 的有效對齊值=min(2,2),由於要求考慮到對齊的情況,從0x0006(因為0x0006%2=0)開始,分配2個位元組的地址0x0006~0x0007
目前為止,位址區段的分配情況就是:0x0000~0x0007共8個位元組,同時也保證了Struct C的對齊情況(2位元組對齊,pragma(2)),sizeof(C)=8
請注意這種情況與Struct B的情況有區別,B的sizeof大小為12個位元組,C的sizeof大小為8個位元組。
最後分析#pragma pack(1)這種情況,這種情況非常簡單,對齊值為1,因為1可以被任何資料整除,所以Struct D的成員變數存取順序是連續的,這樣就好辦了,sizeof(D)=sizeof(int)+sizeof(char)+sizeof(short)=4+1+2=7 (比如從0x0000~0x0006)
總結
在考慮位元組對齊時要細心,搞清楚幾個重要的概念,如類型自身對齊值,手工對齊值以及有效對齊值,有效對齊值決定了最後的存取方式,有效對齊值等於類型自身對齊值與手工對齊值中較小的一個。理解了這一點,對sizeof運算子對類型或都結構的運算也徹底明白了。