C++虚基类与虚函数的内存布局
一、40字节对象大小的组成结构
在标准C++中,一个包含虚基类和虚函数的类实例会由以下组件构成:
内存结构分解
1 | [对象地址] |
关键点:
- 虚函数表指针会占用8字节(64位系统)或4字节(32位系统)
- 虚基类引入的偏移量表通常需要16字节(包含两个虚基类指针)
- 本类数据成员占用16字节(假设包含两个double类型成员)
- 总内存大小 = 虚函数表指针 + 偏移量表 + 数据成员
二、虚基类继承关系
考虑以下类继承结构:
1 | class Figure { virtual void draw() = 0; }; // 虚基类 |
继承树可视化
1 | Circle |
虚基类特性:
- 虚基类在继承链中只会被存储一次(虚继承)
- Circle实例需要维护两个独立的虚基类指针
- 虚基类指针用于定位各自虚基类的虚函数表
- 虚基类的虚函数表指针在内存布局中是"共享"的
三、虚函数表指针布局
在64位系统中,Circular类的内存布局包含:
1 | 1>class Circle size(40): |
虚函数表指针作用:
- vptr1用于调用Figure类的虚函数(draw)
- vptr2用于调用Space类的虚函数(calc)
- 本类的成员函数(如果有的话)会单独构成自己的虚函数表
- 通过vptr的双重指向实现多继承的多态
四、虚继承对内存对齐的影响
虚继承会带来以下对齐特性变化:
内存对齐机制
1 | struct Figure { |
虚继承影响:
- 虚基类指针需要额外的空间(每8字节)
- 对象内存必须对齐到虚基类指针的边界
- 虚基类的内存地址可以通过偏移量表计算
- 编译器通过插入偏移量表确保正确的虚基类访问
五、虚函数调用动态绑定流程
动态绑定过程遵循以下步骤:
调用过程图示
1 | 调用虚函数时: |
动态绑定细节:
- 首先通过vptr找到虚函数表
- 通过虚函数表中的指针定位具体实现
- 对于多继承情况,需要处理多个vptr
- 虚基类的虚函数调用需通过偏移量表调整this指针
六、虚基类与虚函数交互特性
关键交互规则总结如下:
交互规则表格
特性 | 行为 | 注意事项 |
---|---|---|
虚基类偏移量 | 包含两个虚基类指针 | 需要正确初始化 |
虚函数表管理 | 每个虚基类有自己的虚函数表 | 可能导致多个vptr |
内存布局 | 虚基类指针 + 数据成员 | 保证对齐要求 |
隐式调用 | 通过vptr实现多态 | 注意this指针调整 |
需要注意:
- 虚基类的虚函数表在多继承场景下可能需要特殊处理
- 虚函数调用时,需要考虑虚基类的偏移量
- 对象大小包含所有虚基类指针和本类数据成员
- 虚继承会增加内存开销,但避免了菱形继承问题
差异对比
特性 | 虚基类 | 普通基类 |
---|---|---|
内存占用 | 工作指针(8B) | 单个vptr(8B) |
初始化顺序 | 先初始化虚基类 | 定义顺序依次初始化 |
类型检查 | 需要显式指定 | 自动确定 |
继承结构 | 可能需要偏移量表 | 直接继承 |
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.