字節對齊的原因
為了提高 CPU 的存儲速度,編譯器會對 struct 和 union的存儲進行優化,即進行字節對齊。
對齊方式
對於 struct 或 union 中的 struct 或者 union 來說,它們的字節對齊標准就是它的所有成員中字節數最大的數據的字節數。
一般情況下 C/C++ 的變量所占用的字節數
char: 1字節;
short: 2字節;
int: 4字節;
long: 4字節;
long long: 8字節;
float: 4字節;
double: 8字節;
bool: 1字節;
*struct 中字節對齊需要滿足的條件:
1、某個變量存放的起始位置相對於結構的起始位置的偏移量是該變量字節數的整數倍;
2、結構所占用的總字節數是結構種字節數最長的變量的字節數的整數倍。
例:
1 struct Struct 2 { 3 double d1; 4 char d2; 5 int d3; 6 }a;
sizeof(a) = 8 + 1 + 3 + 4 = 16。其中補上的 3 個字節是為了讓 int 型數據的起始位置相對於結構起始位置的偏移量為 4 的整數倍。
1 struct Struct 2 { 3 char d1; 4 double d2; 5 int d3; 6 }b;
sizeof(b) = 1 + 7 + 8 + 4 = 20。 20 / 8 = 2 …… 4,所以需要再補上 4 個字節,使之成為 8 的 整數倍
*union 中字節對齊需要滿足的兩個條件:
1、unoin 的大小必須足夠容納最寬的成員;
2、union 的大小需要能夠被其所包含的基礎成員類型的大小所整除。
字節對齊的另一種方式
VC提供了 #pragma pack(n) 用來自定義字節對齊方式
有一下兩種情況:
1、n 大於變量的字節數:偏移量只滿足默認的字節對齊方式;
2、n 小於變量所占的字節數:偏移量是 n 的整數倍,不使用默認的字節對齊方式。
例:
1 #pragma pack(push) // 保持對齊狀態 2 #pragma pack(4) // 設定為 4 字節對齊 3 struct test 4 { 5 char m1; 6 double m2; 7 int m3; 8 }a; 9 #pragma pack(pop) // 恢復對齊狀態 10 sizeof(a) = 1 + 3 + 8 + 4 = 16 // 其中補上三位是因為 n 小於 8,所以 m2 的起始位置相對於結構起始位置的偏移量是 n,即為 4. 11 12 13 #pragma pack(8) 14 struct S1 15 { 16 char a; 17 long b; 18 }; 19 struct S2 20 { 21 char c; 22 struct S1 d; 23 long long e; 24 }; 25 #pragma pack() 26 27 sizeof(S1) = 1 + 3 + 4 = 8 28 sizeof(S2) = 1 + 3 + 8 + 4 + 8 = 24。// 其中加上的 4 是因為變量 e 的字節數是 8 ,其相對與起始位置的偏移量必須是 8 的倍數。
字節對齊問題的討論到上邊已經結束了。下面再加上我碰到的兩道題目作為實例:
1、
1 #include <stdio.h> 2 union 3 { 4 char x[5]; 5 int i; 6 }a; 7 8 int main() 9 { 10 a.x[0] = 10; 11 a.x[1] = 1; 12 printf("%d\n", a.i); 13 printf("%d\n", sizeof(a)); 14 return 0; 15 }
輸出為 266 8
解析:
對 union 分配內存涉及字節對齊問題,在上方已有詳細描述,在此只簡單解釋一番。union 分配的內存必須是 union 中所有基本數據類型的倍數。
在此題中即為 1 和 4 的倍數,又 char x[5] 占用 5 個字節,故 union 分配的內存大小應為 8 個字節。
windows 系統中高字節在后,低字節在前。而 char x[5] 只有前兩個元素有值,即兩個值只占 2 個字節,也即 union 中的 int 型數據中只有低兩位上有值。
即 i 的二進制表示為:
00000000 00000000 00000001 00001010
即: 2^1 + 2^3 + 2^8 = 266
也相當於十六進制 0x010A, 即: 10 * 16^0 + 1 * 16^2 = 266
這里補充一下進制轉換問題,也是我一並搜集來的:
進制互相轉換
十進制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
十六進制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
二進制 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
十六進制數字轉換十進制數字:
0x2AF5轉換十進制:
5 * 16^0 = 5
F * 16^1 = 240
A * 16^2 = 2560
2 * 16^3 = 8192
---------------
5 + 240 + 2560 + 8192 = 10997
十進制數字轉換十六進制數字:
2500轉換十六進制:
2500 / 16 = 156 …… 4
156 / 16 = 9 …… 12(C)
9 / 16 = 0 …… 9
----------------------
得到4C9,倒轉9C4,即2500的十六進制表示是: 0x9C4
二進制數字轉換十六進制數字:
101110011011.1001
采取四合一法,即以二進制的小數點為分界點,向左(或向右)每四位取一位。
組分好以后,對照二進制與十六進制數的對應表,將四位二進制按權相加,得到的數就是一位十六進制數,然后按順序排列,小數點的位置不變。
結果為:B9B.9
需要注意的是,在向左(或向右)取四位時,取到最高位(最低位)如果無法湊足四位,就可以在小數點的最左邊(或最右邊)補0,進行換算。
十六進制數字轉換二進制數字:
對照二進制與十六進制數的對應表,將十六進制數分為二進制數字,用四位二進制數字按權相加,最后得到二進制數字,小數點依舊。
2、
1 #include <iostream> 2 using namespace std; 3 4 typedef struct A 5 { 6 char aChar; 7 int aInt_2; 8 short aInt; 9 }TypeA; 10 11 typedef struct B 12 { 13 char bChar[3]; 14 TypeA bA; 15 double bDouble; 16 }TypeB; 17 18 int main() 19 { 20 TypeA a; 21 TypeB b; 22 cout << sizeof(a) << endl << sizeof(b) << endl; 23 return 0; 24 }
輸出為 12 24
解析:
由上述對字節對齊問題的討論很容易便可以得出此題的答案。
sizeof(TypeA) = 1 + 3 + 4 + 2 = 10。 10 / 4 = 2 …… 2,故需要再補上 2 個字節,即 sizeof(TypeA) = 12;
sizeof(TypeB) = 3 + 1 + 12 + 8 = 24。之所以補上 1 個字節是因為 TypeA 類型在 struct TypeB 中的默認對齊方式
是 4 個字節(即 int 的字節大小,也即 struct TypeA 中最長的字節)。