✏️

Memory Model

Tags

为啥要内存对齐

  • 性能问题
    • CPU一次读一个字,且每次读取都是直接从字的整数倍开始读
    • 如果对齐则能保证长度在一个字以内的内存能被一次读完,否则就可能要两次 + 截断,
notion image
  • 跨平台支持
    • 部分硬件架构就是不支持内存不对齐的读取

class / struct内存对齐

默认对齐规则:
  • 定义「有效对齐值」= min ( #pragma pack(n)的n(或者通过其他方式设置的手动对齐值), 结构体/类中最宽成员的字节数 ),#pragma pack(n)的n默认为系统字长(32位则4,64位则8)
  • 结构体首地址 可以整除 有效对齐值
  • 每个成员的首地址与结构体地址的offset 可以整除 min ( 有效对齐值, 自己的字节数 )(这也是为什么建议从大到小、内存对齐的元素优先声明)
  • 结构体大小 可以整除 有效对齐值
如果想手动控制对齐规则,可以使用下面的几个方法:
  • #pragma pack
    • 设置的手动对齐值是2^n字节 (n整,0 ≤ n ≤ 4)
// 手动指定对齐,强制1字节对齐(禁用填充) #pragma pack(push, 1) class NetworkPacket { uint8_t header; // 0 B uint32_t data; // 1~4 B,不加对齐应该是4~7 B }; // 恢复默认对齐 #pragma pack(pop)
  • alignas (C++11)
alignas(32) class AlignedData { // C++11 起支持,32字节对齐 float values[8]; // 用于 AVX 指令 };
  • __attribute__((aligned(n))) (GCC/Clang的属性语法)
  • 这个规定结构体起始地址和结构体大小必须被n整除,与有效对齐值有区别
typedef struct mystruct1 { //in64_64 in32_32(可以通过#pragma pack(4)实现) int a; //(4 + 4) 4 double b;// 8 8 char c; //(1 + 15) (1 + 3) }__attribute__((aligned(16))) AMS1;//in 64_64:32; in 32_32:16

class / struct对象模型总体原则

先看个大概:
notion image
  • 按照字段声明顺序分配内存(注意任意非虚方法是存在.text段的)
  • 存在继承的:vfptr和字段内存从祖先基类 → 自己依次分配(虚继承除外,虚基类的实例会被存在末尾)
  • 有虚方法的:对象内存的首地址是一个指向虚方法表的vfptr
  • 存在多重继承:
    • 第一个虚方法表:第一个基类 | 自己独有的
    • 然后跟第一个基类的字段
    • 之后存其他基类的虚方法表指针。其他虚方法表存的就是其它基类独有的虚方法
    • 若多个基类都实现了同一虚方法,则多张虚方法表中都会有这个函数
  • 存在虚继承:
    • 第一个虚方法表:不装虚基类的虚方法,只装自己独有的
    • 紧接其后存储的是有关虚基类的虚方法表的vbptr:实际上存储的是 虚基方法表指针 与 vbptr所在位置 的偏移
    • 虚基类的vfptr和字段单独存储在末尾
    • 注:如果派生类没有自己独有的虚方法,则开头
  • 嵌套后,规则也进行嵌套即可。比如单继承 + 多继承、钻石继承
 
深度为2的单+多继承:
notion image
注意CChildren的内存是在两个Parent的vfptr和字段都分配结束了之后才分配的
注意CChildren的内存是在两个Parent的vfptr和字段都分配结束了之后才分配的
 
 
钻石继承 + 虚继承
notion image
_vbptr解释:前面的数字(24)表示的就是 这个虚基表指针对应的虚方法指针相对于当前位置的偏移
_vbptr解释:前面的数字(24)表示的就是 这个虚基表指针对应的虚方法指针相对于当前位置的偏移
 
 
Reference: