C++虚多继承场景下dynamic_cast得到的p与p2为何地址相同
测试代码
#include <iostream> using namespace std; class B { int i; public: B() { i = 1; } virtual int get_i() { return i; } }; class D : virtual public B { int j; public: D() { } }; class D2 : virtual public B { int j2; public: D2() {} }; class MM : public D, public D2 { int x; public: MM() { } }; int main() { B* o = new MM(); MM* p = dynamic_cast<MM*>(o); D* p2 = dynamic_cast<D*>(o); return 0; }
原理解释
该测试代码运行时观测到的MM*类型指针p与D*类型指针p2地址完全一致,是主流C++编译器对多继承、虚继承内存布局的常规实现结果,不存在逻辑矛盾:
- 虚继承解决菱形继承数据冗余的核心逻辑,是让最终派生类
MM的内存空间中仅存在一份共享的虚基类B子对象,中间派生类D、D2不再各自持有独立的B副本,而是通过虚基类偏移表间接寻址访问这份共享的B实例。 - 主流编译器(GCC、Clang、MSVC)的多继承布局规则中,派生类的第一个非虚基类子对象会被放置在整个派生类对象内存块的起始位置,二者地址完全重合。
MM的继承顺序为先继承D、再继承D2,因此MM完整对象的起始地址,与其内部嵌套的D子对象起始地址天然一致。 - 预判三个指针地址应当相同的认知误区,在于混淆了虚基类子对象的存放位置:作为虚基类的
B是跨路径共享的子对象,编译器不会将其放置在派生类对象的起始位置(否则无法适配多继承路径下的布局一致性要求),因此B*类型指针o存储的是MM对象内部共享B子对象的独立地址,与p、p2的地址值不同,这是虚继承布局的正常表现。 dynamic_cast做基类到派生类的跨层级转换时,不会直接做固定地址偏移,而是通过对象的RTTI信息查询目标类型对应的子对象偏移量:从B*转换到MM*时,运行时会计算从B子对象地址到MM完整对象起始地址的偏移;从B*转换到D*时,运行时计算得到的偏移指向MM内部嵌套的D子对象地址,而该D子对象恰好位于MM对象的起始位置,因此两次转换得到的指针值完全相等。
C++标准未对对象内存布局做强制统一规定,上述为当前工业界主流编译器的通用实现逻辑,不同编译器可能存在细节差异,但虚基类共享存储、首个非虚基类与派生类对象起始地址重合的设计逻辑是通用的。
内容的提问来源于stack exchange,提问作者ionescu radu
相关产品推荐
相关产品推荐

