一、40字节对象大小的组成结构

在标准C++中,一个包含虚基类和虚函数的类实例会由以下组件构成:

内存结构分解

1
2
3
4
[对象地址] 
├── 虚函数表指针 (8字节) → 用于多态调用
├── 偏移量表 (16字节) → 处理虚基类偏移
└── 数据成员 (16字节) → 本类的实际数据

关键点

  1. 虚函数表指针会占用8字节(64位系统)或4字节(32位系统)
  2. 虚基类引入的偏移量表通常需要16字节(包含两个虚基类指针)
  3. 本类数据成员占用16字节(假设包含两个double类型成员)
  4. 总内存大小 = 虚函数表指针 + 偏移量表 + 数据成员

二、虚基类继承关系

考虑以下类继承结构:

1
2
3
4
5
6
7
class Figure { virtual void draw() = 0; }; // 虚基类
class Space { virtual void calc() = 0; }; // 虚基类
class Circle : virtual public Figure, virtual public Space {
double radius;
double area;
double circumference;
};

继承树可视化

1
2
3
        Circle
/ \
Figure (虚基类) Space (虚基类)

虚基类特性

  1. 虚基类在继承链中只会被存储一次(虚继承)
  2. Circle实例需要维护两个独立的虚基类指针
  3. 虚基类指针用于定位各自虚基类的虚函数表
  4. 虚基类的虚函数表指针在内存布局中是"共享"的

三、虚函数表指针布局

在64位系统中,Circular类的内存布局包含:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1>class Circle size(40):
1> +---1> 0 | +--- (base class Figure)
1> 0 | | {vfptr}
1> | +---
1> 8 | {vbptr}
1>16 | r
1> | <alignment member> (size=4)
1> +---
1>28 | (vtordisp for vbase Space)
1> +--- (virtual base Space)
1>32 | {vfptr}
1> +---

[对象地址]
├── vptr1 (8字节) → 指向Figure的虚函数表
├── vptr2 (8字节) → 指向Space的虚函数表
└── data (16字节) → radius, area, circumference

虚函数表指针作用

  1. vptr1用于调用Figure类的虚函数(draw)
  2. vptr2用于调用Space类的虚函数(calc)
  3. 本类的成员函数(如果有的话)会单独构成自己的虚函数表
  4. 通过vptr的双重指向实现多继承的多态

四、虚继承对内存对齐的影响

虚继承会带来以下对齐特性变化:

内存对齐机制

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Figure {
void* vptr1; // 8字节
}; // 虚基类对齐要求

struct Space {
void* vptr2; // 8字节
}; // 虚基类对齐要求

struct Circle : virtual public Figure, virtual public Space {
double radius; // 8字节
double area; // 8字节
double circumference; // 8字节
};

虚继承影响

  1. 虚基类指针需要额外的空间(每8字节)
  2. 对象内存必须对齐到虚基类指针的边界
  3. 虚基类的内存地址可以通过偏移量表计算
  4. 编译器通过插入偏移量表确保正确的虚基类访问

五、虚函数调用动态绑定流程

动态绑定过程遵循以下步骤:

调用过程图示

1
2
3
4
5
调用虚函数时:
1. 通过vptr1访问Figure的虚函数表
2. 通过vptr2访问Space的虚函数表
3. 检查虚函数表中的函数指针
4. 调用实际对象的对应函数实现

动态绑定细节

  1. 首先通过vptr找到虚函数表
  2. 通过虚函数表中的指针定位具体实现
  3. 对于多继承情况,需要处理多个vptr
  4. 虚基类的虚函数调用需通过偏移量表调整this指针

六、虚基类与虚函数交互特性

关键交互规则总结如下:

交互规则表格

特性 行为 注意事项
虚基类偏移量 包含两个虚基类指针 需要正确初始化
虚函数表管理 每个虚基类有自己的虚函数表 可能导致多个vptr
内存布局 虚基类指针 + 数据成员 保证对齐要求
隐式调用 通过vptr实现多态 注意this指针调整

需要注意

  1. 虚基类的虚函数表在多继承场景下可能需要特殊处理
  2. 虚函数调用时,需要考虑虚基类的偏移量
  3. 对象大小包含所有虚基类指针和本类数据成员
  4. 虚继承会增加内存开销,但避免了菱形继承问题

差异对比

特性 虚基类 普通基类
内存占用 工作指针(8B) 单个vptr(8B)
初始化顺序 先初始化虚基类 定义顺序依次初始化
类型检查 需要显式指定 自动确定
继承结构 可能需要偏移量表 直接继承