数据宽度:占多少字节

  • 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 的内存布局可按常见平台的规则理解:

  1. 依声明顺序放成员:每个成员的起始地址必须是其对齐值的整数倍。
  2. 确定结构体的对齐要求:常见情况下等于各成员对齐值的最大值,具体由平台和编译器决定。
  3. 补齐末尾:结构体大小是其对齐值的整数倍;最后一个成员之后可能需要填充字节。

结构体起始地址本身也要满足其对齐要求。下面假设 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 排列。?? 表示不能依赖填充字节的值。