C++ 虚继承、虚基类指针、虚基类表

发布于:2025-03-22 ⋅ 阅读:(59) ⋅ 点赞:(0)

没有使用虚继承情况下

#include<iostream>

class Base {
public:
	int m_Age1; // 定义公共成员变量
};

class derived:public Base{
public:
	int m_Age2;// 定义公共成员变量
};
int main() {

	return 0;
}

derived 类的对象模型: 

 

使用虚继承后

#include<iostream>

class Base {
public:
	int m_Age1; // 定义公共成员变量
};

class derived:virtual public Base{
public:
	int m_Age2;// 定义公共成员变量
};
int main() {

	return 0;
}

derived 类的对象模型: 

这幅图片展示了 C++ 代码的编译结果,分析的是一个 derived 类的内存布局以及虚表(vtable)和虚基表(vbtable)信息。以下是详细分析:

1. derived 的内存布局

class derived    size(20):

derived 类的大小为 20 字节,表明该类可能包含指针(通常为 8 字节,在 64 位系统上)和一些数据成员。

成员变量的布局

0   | {vbptr}  // 虚基类指针(vbptr)
8   | m_Age2   // 普通成员变量
    | <alignment member> (size=4)  // 对齐填充
16  | m_Age1   // 虚基类 Base 的成员变量
  • 偏移 0 处的 {vbptr}
    这是 虚基类指针(vbptr),用于指向虚基类表(vbtable)。当一个类有虚基类时,编译器需要额外的指针来解析虚基类的偏移,从而支持动态多态。

  • 偏移 8 处的 m_Age2
    这是 derived 类自己的成员变量 m_Age2,紧随 vbptr 之后。

  • 对齐填充(alignment member,size=4)
    由于 m_Age2 的大小可能导致对齐问题(例如,它可能是 4 字节,而后续成员需要 8 字节对齐),编译器插入 4 字节填充 以保证对齐。

  • 偏移 16 处的 m_Age1
    这是 Base(虚基类)的成员变量 m_Age1,位于偏移 16 处,表明 Basederived 类中的起始地址是 16。

2. 虚基表(vbtable)

derived::$vbtable@:
 0 | 0
 1 | 16 (derived(derived+0)Base)

vbtable 记录了虚基类 Basederived 类中的偏移信息:

  • 索引 0: 存储 0,通常是用于虚拟基类解析的占位符。
  • 索引 1: 存储 16,表示 Basederived 类中的偏移量是 16 字节。

3. 虚基表信息

vbi:
  class   offset  o.vbptr  o.vbte  fVtorDisp
  Base    16      0        4       0
  • Base 作为虚基类
    • offset = 16,表示 Basederived 类中的实际偏移量。
    • o.vbptr = 0,表示 Base 本身没有虚基类指针。
    • o.vbte = 4,表示 Base 的虚基表条目在 vbtable 中的偏移量。
    • fVtorDisp = 0,表示没有额外的构造/析构调整。

总结

  1. derived 继承自 Base,且 Base虚基类,导致 derived 需要 vbptr 来存储虚基类偏移信息。
  2. derived 的内存布局为:
    • 8 字节用于 vbptr
    • m_Age2 在偏移 8
    • 4 字节的填充用于对齐
    • m_Age1 在偏移 16(属于虚基类 Base
  3. vbtable 记录了 Basederived 类中的偏移量(16)。
  4. 由于虚基类的存在,derived 类的大小 增加 了(否则可能会更小)。

这个输出通常由 clangmsvcclang -fdump-record-layoutsdumpbin 生成,可用于分析 C++ 类的内存布局和虚拟继承机制的实现。

 

 

虚基类指针(vbptr)的归属

虚基类指针(vbptr并不属于类本身,而是每个对象实例中的一部分。它用于在多继承虚基类的情况下,帮助对象正确解析虚基类的存储位置。

然而,即使没有实例化对象,编译器仍然能够静态分析类的内存布局,并报告 vbptr 的存在。这是因为:

  1. 类的内存布局在编译期就确定了
    编译器需要为 derived 计算所需的存储空间,并决定对象的内存分布,即便还没有实际分配对象。这个过程中,它会识别出 Basederived 的虚基类,因此必须在 derived 的实例中存储 vbptr

  2. 虚基类指针 vbptr 归属于类的实例,而不是类本身

    • vbptr对象的一部分,每个 derived 的实例都会有自己的 vbptr,指向相应的 vbtable
    • vbtable(虚基表)是类级别的数据结构,所有 derived 的实例共享相同的 vbtable

网站公告

今日签到

点亮在社区的每一天
去签到