数据宽度:占多少字节
- bit(位):一个
0或1。 - Byte(字节):内存寻址的基本单位。在常见的 8 位字节机器上,
1 Byte = 8 bit。
例如,uint32_t x = 0x12345678; 在提供 uint32_t 且字节为 8 位的环境中占 4 字节,可拆成 12 34 56 78,每两个十六进制数字对应一个字节。uint32_t 定义在 <stdint.h> 中;C 语言并不保证所有平台都有这个类型,也不保证一个字节总是 8 位。
MSB(Most Significant Bit,最高有效位)和 LSB(Least Significant Bit,最低有效位)分别是二进制数中权重最大和最小的位。例如 1011 0010 的 MSB 是左端的 1,LSB 是右端的 0。这里的“高、低”说的是数值权重,并非内存地址。
字节序:多个字节怎么排
假设 x = 0x12345678 从地址 0x1000 开始存储:
讨论字节序时,也有人用 MSB/LSB 指最高/最低有效字节:这个例子中分别是 0x12 和 0x78。为避免与“最高/最低有效位”混淆,下面直接写“高位字节”和“低位字节”。
| 地址 | 0x1000 |
0x1001 |
0x1002 |
0x1003 |
|---|---|---|---|---|
| 大端 | 12 |
34 |
56 |
78 |
| 小端 | 78 |
56 |
34 |
12 |
- 大端(Big Endian):高位字节放在低地址,可记为“大头在前”。
- 小端(Little Endian):低位字节放在低地址,可记为“小头在前”。常见的 x86/x86-64 环境采用小端序。
字节序只改变内存中的字节排列,不改变变量的数值:无论哪种排列,作为 uint32_t 读取的 x 仍是 0x12345678。单字节数据没有字节序区别。网络字节序通常采用大端序。
边界对齐:从哪个地址开始放
对齐要求为 4 字节的数据,起始地址通常应是 4 的倍数,例如 0x1000、0x1004;0x1001 则未满足这一要求。合适的对齐便于硬件访问;未对齐访问在不同架构上可能变慢、需要额外处理,或不被允许。
可以用一个简化模型理解原因:假设硬件按 4 字节块处理一次访问,位于 0x1000~0x1003 的 4 字节数据落在同一块中;如果从 0x1002 开始,就会跨到 0x1004~0x1007 这一块,可能需要分两次读取再拼接。有些处理器能自动处理这种情况,有些则对未对齐访问有限制。因此编译器通常按类型的对齐要求安排地址。
常见情形:char 按 1 字节、short 按 2 字节、int 按 4 字节、double 按 8 字节对齐。
C 结构体的内存布局
C 语言中,普通 struct 的内存布局可按常见平台的规则理解:
- 依声明顺序放成员:每个成员的起始地址必须是其对齐值的整数倍。
- 确定结构体的对齐要求:常见情况下等于各成员对齐值的最大值,具体由平台和编译器决定。
- 补齐末尾:结构体大小是其对齐值的整数倍;最后一个成员之后可能需要填充字节。
结构体起始地址本身也要满足其对齐要求。下面假设 char 占 1 字节、int 占 4 字节且按 4 字节对齐,结构体也按 4 字节对齐:
struct A {
char a;
int b;
char c;
};
| 相对起始地址的偏移 | 内容 | 原因 |
|---|---|---|
| 0 | a |
第一个成员 |
| 1~3 | 填充 | 让 b 从 4 的倍数偏移开始 |
| 4~7 | b |
占 4 字节 |
| 8 | c |
占 1 字节 |
| 9~11 | 尾部填充 | 让结构体大小成为 4 的倍数 |
所以这个例子中,offsetof(struct A, b) == 4、offsetof(struct A, c) == 8,sizeof(struct A) == 12。offsetof 定义在 <stddef.h> 中。可以记为:
偏移: 0 1 2 3 4 5 6 7 8 9 10 11
内容: a P P P b b b b c P P P
P 是填充字节,不属于任何成员,不应依赖它的具体内容。尾部填充使结构体数组中的每个元素都能正确对齐:此例中下一个 A 从偏移 12 开始,它的 b 便位于偏移 16。
成员顺序会影响大小
将成员换个顺序:
struct B {
int b;
char a;
char c;
};
在相同假设下,布局为 b b b b | a c P P,因此 sizeof(struct B) == 8。两个结构体的成员相同,但填充位置不同,大小也可能不同。成员的声明顺序不能由编译器随意重排;调整顺序需要修改结构体定义。
对齐和字节序一起看
struct C {
char x;
uint32_t y;
};
假设 x = 0xAA、y = 0x12345678,结构体从 0x1000 开始,y 按 4 字节对齐,机器采用小端序:
地址: 1000 1001 1002 1003 1004 1005 1006 1007
内容: AA ?? ?? ?? 78 56 34 12
x 填充字节 y
对齐决定 y 从 0x1004 开始;小端序决定它的 4 个字节在 0x1004~0x1007 中按 78 56 34 12 排列。?? 表示不能依赖填充字节的值。