仔细讨论 C/C++ 字节对齐问题
字节对齐的原因
为了提高 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 中最长的字节)。