Skip to content

内存布局 ​

内存布局的基础 ​

  • 对齐(Alignment):每个数据类型(如整数、浮点数等)通常有一个对齐要求,这意味着它们必须按特定的字节边界存储,这可以提高内存访问的效率。
  • 填充(Padding):为了满足对齐要求,编译器可能会在数据成员之间插入填充字节。

内存对齐规则 ​

数据项对齐规则
基本数据类型地址 = sizeof(type) 的整数倍
结构体起始地址地址 = max(所有成员对齐值) 的整数倍
结构体成员偏移偏移量 = 该成员对齐值 的整数倍
结构体总大小大小 = max(所有成员对齐值) 的整数倍

规则1:自身对齐值 ​

每个基本数据类型必须存储在其大小整数倍的地址上。

  • char (1字节): 可存放在任何地址(因为任何数都是1的倍数)。
  • short (2字节): 地址必须是2的倍数(偶数地址)。
  • int (4字节): 地址必须是4的倍数。
  • double (8字节): 地址必须是8的倍数。
  • 指针(通常4或8字节): 地址必须是4或8的倍数。

规则2:结构体对齐值 ​

整个结构体的对齐值是其所有成员中最大对齐值。 结构体的起始地址必须是这个最大对齐值的整数倍。

cpp
struct Example {
    char a;      // 对齐值=1
    int b;       // 对齐值=4
    double c;    // 对齐值=8
}; // 该结构体的对齐值是 max(1, 4, 8) = 8

规则3:结构体的大小 ​

整个结构体的大小必须是其最大对齐值的整数倍。 编译器会在结构体末尾添加“填充字节”(Padding)来满足这一要求。

规则4:成员偏移量 ​

结构体内部的每个成员,其相对于结构体起始地址的偏移量(Offset) 必须是该成员自身对齐值的整数倍。 编译器会在成员之间添加“填充字节”来满足这一要求。

编译指令的影响 ​

可以使用编译指令(如GCC/Clang的 __attribute__((packed)) 或MSVC/GCC的 #pragma pack(n))来修改默认的对齐规则。

#pragma pack(n):指定新的对齐系数 n。(通常是 1、2、4、8、16)

  • 每个成员的有效对齐值将变为 min(其自身对齐值,n)
  • 结构体总大小有效对齐值变为 min(成员中最大对齐值, n) 的整数倍

类、结构体的内存布局 ​

类和结构体的成员是按照定义的顺序排列在内存中的,但编译器可能插入填充字节保证对齐。

cpp
struct MyStruct {
    char a;       // 大小=1,对齐值=1
    int b;        // 大小=4,对齐值=4
    short c;      // 大小=2,对齐值=2
    double d;     // 大小=8,对齐值=8
};
  1. 确定结构体对齐值:成员中最大对齐值是 double d 的 8。所以整个结构体的起始地址必须是8的倍数。

  2. 从偏移量0开始放置成员:

    • char a:大小1,对齐值1。放在偏移量 0。0 % 1 == 0,符合。
    • int b:大小4,对齐值4。下一个可用偏移量是1。但 1 % 4 != 0,不符合。需要填充3字节(偏移量1,2,3),然后将其放在偏移量 4。4 % 4 == 0,符合。
    • short c:大小2,对齐值2。下一个可用偏移量是8(4+4)。8 % 2 == 0,符合。将其放在偏移量 8,占用8和9两个字节。
    • double d:大小8,对齐值8。下一个可用偏移量是10。但 10 % 8 != 0,不符合。需要填充6字节(偏移量10-15),然后将其放在偏移量 16。16 % 8 == 0,符合。占用16到23字节。
  3. 计算当前总大小并最终调整:

    • 最后一个成员 d 结束于偏移量23。
    • 当前总大小为24字节。
    • 检查总大小是否为最大对齐值(8)的整数倍?24 % 8 == 0,符合。无需在末尾额外填充。