您的位置:首页 > 编程语言 > C语言/C++

[C++] 虚继承对C++对象内存模型的影响

2014-03-09 00:18 232 查看
测试环境

平台:32位

编译环境:VS2008

虚继承相关背景

如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的属性和方法(假设这些属性和方法是公有的且都是公有继承)。从设计角度讲,这个实现是错误的,它容易产生二义性且浪费内存空间。

虚继承可以解决这个问题,虚继承可以为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝。

问题:从一个例子开始

假设有虚继承结构如下,虚基类A中有int成员a_;B1虚继承自A,有int成员b1_;B2虚继承自A,有int成员b2_;D继承自B1、B2,有int成员d_。



在32位架构下,类A、B1、B2、D的大小?哎!我比较笨!只好写个程序看看结果先。

#include <iostream>
using namespace std;

class A
{
public :
int a_ ;
};

class B1 : virtual public A
{
public :
int b1_ ;
};

class B2 : virtual public A
{
public :
int b2_ ;
};

class D : public B1, public B2
{
public :
int d_ ;
};

int main (void)
{
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(B1) = " << sizeof(B1)<< endl;
cout << "sizeof(D) = " << sizeof(D) << endl;

return 0;
}


运行结果如下:



sizeof(A)的结果符合我们的预期,但是sizeof(B1)和sizeof(D)的结果就有点不能理解了。

分析:从查看B1类对象成员属性的地址开始

我们不妨先构造一个B1类对象,然后打印该对象中的各个属性地址来看看究竟。

#include <iostream>
using namespace std;

class A
{
public :
int a_ ;
};

class B1 : virtual public A
{
public :
int b1_ ;
};

class B2 : virtual public A
{
public :
int b2_ ;
};

class D : public B1, public B2
{
public :
int d_ ;
};

int main (void)
{
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(B1) = " << sizeof(B1)<< endl;
cout << "sizeof(D) = " << sizeof(D) << endl << endl;

B1 ob1;
cout << "&ob1 = 0x" << &ob1 << endl;
cout << "&ob1.a_ = 0x" << &ob1.a_ << endl;
cout << "&ob1.b1_ = 0x" << &ob1.b1_ << endl;

return 0;
}


运行结果如下:



我们发现ob1的首地址既不等于成员a_的地址也等于成员b1_的地址。那从首地址开始的4个字节存放的是什么内容呢?

分析:B1类对象内存模型

下面来分析原理,根据刚才获取的B1类大小以及该类对象中各个属性的地址分布,绘制B1类内存模型图如下:



从内存模型图中可以看出:该对象的内存分为B1部分和虚基类部分。从B1部分的首地址开始的4个字节存放的是一个vbptr(B1部分的虚基类表指针),该指针指向了一个虚基类表(virtual base table of B1),这个虚基类表中存放了:

B1部分首地址vbptr(B1)所在地址之差,即0x0013F8AC - 0x0013F8AC = 0。

虚基类部分首地址vbptr(B1)所在地址之差,即0x0013F8B4 - 0x0013F8AC = 8。

好了,我们来验证下该内存模型是否正确。

#include <iostream>
using namespace std;

class A
{
public :
int a_ ;
};

class B1 : virtual public A
{
public :
int b1_ ;
};

class B2 : virtual public A
{
public :
int b2_ ;
};

class D : public B1, public B2
{
public :
int d_ ;
};

int main (void)
{
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(B1) = " << sizeof(B1)<< endl;
cout << "sizeof(D) = " << sizeof(D) << endl << endl;

B1 ob1;
cout << "&ob1 = 0x" << &ob1 << endl;
cout << "&ob1.a_ = 0x" << &ob1.a_ << endl;
cout << "&ob1.b1_ = 0x" << &ob1.b1_ << endl << endl;

long ** pVbptr = (long **)&ob1;
cout << "virtual base table of B1 : " << endl;
cout << "  [0] : " << pVbptr[0][0] << endl;
cout << "  [1] : " << pVbptr[0][1] << endl;

return 0;
}


运行结果如下:



分析:D类对象内存模型

有了前面的分析,下面的分析就简单多了。我们还是构造一个D类对象,然后打印下该对象中各个属性的地址。

#include <iostream>
using namespace std;

class A
{
public :
int a_ ;
};

class B1 : virtual public A
{
public :
int b1_ ;
};

class B2 : virtual public A
{
public :
int b2_ ;
};

class D : public B1, public B2
{
public :
int d_ ;
};

int main (void)
{
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(B1) = " << sizeof(B1)<< endl;
cout << "sizeof(D) = " << sizeof(D) << endl << endl;

D od;
cout << "&od = 0x" << &od << endl;
cout << "&od.a_ = 0x" << &od.a_ << endl;
cout << "&od.b1_ = 0x" << &od.b1_ << endl;
cout << "&od.b2 = 0x" << &od.b2_ << endl;
cout << "&od.d_ = 0x" << &od.d_ << endl;

return 0;
}


运行结果如下:



根据运行结果绘制D类对象内存模型图如下:



至于原理:在前面也已经说明了,这里就不再赘述了。

扩展:访问D类对象的成员变量是如何实现的?

当有一个虚基类指针指向了派生类对象,那么这次指向只是简单的赋值吗?

#include <iostream>
using namespace std;

class A
{
public :
int a_ ;
};

class B1 : virtual public A
{
public :
int b1_ ;
};

class B2 : virtual public A
{
public :
int b2_ ;
};

class D : public B1, public B2
{
public :
int d_ ;
};

int main (void)
{
D od;
A* pa;
pa = &od;

return 0;
}


我们在32行设置下断点,单步运行后发现:



pa真正的值不等于&od,更有意思的是它们的偏移量为0x001DFEBC - 0x001DFEA8,也就是20(10进制)。

因此可以确定的是:在pa=&od执行时,程序内部会先通过该对象中的vbptr,然后通过vbptr(虚基类表指针)找到vbtbl(虚基类表),最终在vbtbl中找到虚基类表中虚基类部分与vbptr的偏移值,然后将指针指向加上偏移值的地址。

总结

本文针对虚继承情况,分析了在32位架构+VS2008编译环境下类对象内存模型,虽然有与分析内容相符合的运行结果,但这并不代表所有编译条件下(如g++)构造的内存模型都如上述情况(不同编译器:虚基类部分在整个对象模型中的位置会有些差异)。不过话又说回来,它们生成对象模型的原理其实都是类似的。

(完)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: