C++ 对象内存布局 (5)

下面来看看虚基类对对象内存布局的影响。虚基类的主要作用就是在所有的派生类中,保留且仅保留一份虚基类的suboject。

 

a. 一个虚基类的情况

#include <iostream>

using namespace std;

 

class Base

{

public:

         int base_member;

};

 

class Derived : public virtual Base {};

 

int main(void)

{

         Base b;

         Derived d;

 

         cout << sizeof(b) << endl;

         cout << sizeof(d) << endl;

 

         return 0;

}

运行结果:

 

 

注意使用了虚继承,即class Derived : public virtual Base。这次Derived的对象大小为什么为8 bytes呢?这是因为编译器会给Derived对象安插一个虚基类表指针vbptr,下面给出Derived对象的memory layout:

 

 

虚基类表指针vbptr指向Derived类的virtual bass class table(虚基类表)虚基类表中存放的是Derived类的虚基类表指针到虚基类实例指针的偏移量。

 

如果Derived有两个虚基类,那么这两个虚基类的实例指针,分别位于虚基类表的第2项和第3项,最后一项为0,意味着虚基类表的结束。虚基类表中的第1项是什么?目前不清楚,但是它的值大部分时候为0(请牛人指教,编译器是VC6)。下面我们来检验之。

 

在main函数的return前,增加如下语句:

         cout << "Derived object d's vbptr = " << (unsigned long*)(&d) << endl;

         cout << "Address of virtual base class table = " << (unsigned long*)*(unsigned long*)(&d) << endl;

         cout << "Item 1 in virtual base class table = " << *(unsigned long*)*(unsigned long*)(&d) << endl;

         cout << "Item 2 in virtual base class table = " << *((unsigned long*)*(unsigned long*)(&d) + 1) << endl;

         cout << "Item 3 in virtual base class table = " << *((unsigned long*)*(unsigned long*)(&d) + 2) << endl;

 

         cout << "The address of virtual base class Base = " << (Base*)(&d) << endl;

编译后运行结果:

不难发现,虚基类示例地址 = vbptr + offset,即0x0012FF78 = 0x0012FF74 + 4。图示如下:

 

b. 我们来看看Derived有两个虚基类的情况:

#include <iostream>

using namespace std;

class Base1

{

public:

         int base1_member;

};

 

class Base2

{

public:

         int base2_member;

};

 

class Derived : public virtual Base1, public virtual Base2 {};

 

int main(void)

{

         Base1 b1;

         Base2 b2;

         Derived d;

 

         cout << sizeof(b1) << endl;

         cout << sizeof(b2) << endl;

         cout << sizeof(d) << endl;

 

         cout << "Derived object d's vbptr = " << (unsigned long*)(&d) << endl;

         cout << "Address of virtual base class table = " << (unsigned long*)*(unsigned long*)(&d) << endl;

         cout << "Item 1 in virtual base class table = " << *((unsigned long*)*(unsigned long*)(&d) + 0) << endl;

         cout << "Item 2 in virtual base class table = " << *((unsigned long*)*(unsigned long*)(&d) + 1) << endl;

         cout << "Item 3 in virtual base class table = " << *((unsigned long*)*(unsigned long*)(&d) + 2) << endl;

         cout << "Item 3 in virtual base class table = " << *((unsigned long*)*(unsigned long*)(&d) + 3) << endl;

 

         cout << "The address of virtual base class: Base1's instance = " << (Base1*)(&d) << endl;

         cout << "The address of virtual base class: Base2's instance = " << (Base2*)(&d) << endl;

 

         return 0;

}

编译运行结果如下:

 

Derived对象的memory layout图解如下:

 

 

不管Derived类有多少个虚基类,它只有一个vbptr和一个virtual base class table。

 

继续探讨虚基类对对象内存布局的影响。几个类的继承关系如下图,这是虚基类最为常见的用法之一:

 

代码如下:

#include <iostream>

using namespace std;

 

class Base

{

public:

         int base_member;

};

 

class Derived1 : public virtual Base

{

public:

         int derived1_member;

};

 

class Derived2 : public virtual Base

{

public:

         int derived2_member;

};

 

class ChildDerived : public Derived1, public Derived2

{

public:

         int childderived_member;

};

 

 

int main(void)

{

         ChildDerived cd;

 

         cout << "sizeof(Base) = /t/t" << sizeof(Base) << endl;

         cout << "sizeof(Derived1) = /t" << sizeof(Derived1) << endl;

         cout << "sizeof(Derived2) = /t" << sizeof(Derived2) << endl;

         cout << "sizeof(ChildDerived) = /t" << sizeof(ChildDerived) << endl;

 

         cout << endl;

 

         cout << "Derived1 subobject's vbptr = " << (unsigned long*)&cd << endl;

         cout << "Derived2 subobject's vbptr = " << (unsigned long*)&cd + 2 << endl;

 

         cout << endl;

 

         cout << "Address of virtual base class table of Derived1 object = " << (unsigned long*)(*((unsigned long*)&cd)) << endl;

         cout << "Item 1 in Derived1's virtual base class table = " << *((unsigned long*)*(unsigned long*)(&cd) + 0) << endl;

         cout << "Item 2 in Derived1's virtual base class table = " << *((unsigned long*)*(unsigned long*)(&cd) + 1) << endl;

         cout << "Item 3 in Derived1's virtual base class table = " << *((unsigned long*)*(unsigned long*)(&cd) + 2) << endl;

 

         cout << endl;

 

         cout << "Address of virtual base class table of Derived2 object = " << (unsigned long*)*((unsigned long*)&cd + 2) << endl;

         cout << "Item 1 in Derived2's virtual base class table = " << *((unsigned long*)*((unsigned long*)&cd + 2) + 0) << endl;

         cout << "Item 2 in Derived2's virtual base class table = " << *((unsigned long*)*((unsigned long*)&cd + 2) + 1) << endl;

         cout << "Item 3 in Derived2's virtual base class table = " << *((unsigned long*)*((unsigned long*)&cd + 2) + 2) << endl;

 

         cout << endl;

 

         cout << "The address of base class Derived1's instance = " << (Derived1*)&cd << endl;

         cout << "The address of base class Derived2's instance = " << (Derived2*)&cd << endl;

         cout << "The address of base class Base's instance = /t" << (Base*)&cd << endl;

 

         return 0;

}

 

运行结果如下:

 

sizeof(Base) = 4 bytes,这个是很好解释的,因为Base中由一个类型为int的成员变量。

sizeof(Derived1) = 12 bytes和sizeofDerived2) = 12 bytes,图解如下:

 

 

两个“?”到底是多少我们且不去管它,等我们讨论ChildDerived对象的memory layout时再进行详细说明。从上图可以看到,Base subobject在最后。

 

现在我们来看看ChildDerived对象的memory layout的情况:

由于Derived1和Derived2都是从Base虚拟继承而来,因而Base是它们的虚基类,编译器在编译的时候会分别给他们安插vbptr指针;Derived1和Derived2同时被ChildDerived普通继承(非虚继承),根据C++标准的要求,基类在在派生类中保证其原始的完整性,因此两个vbptr被继承到了ChildDerived类;由于Base被虚继承,可以看到Base suboject只有一份拷贝,而且放在最后。

 

从下面图解可以很清楚地发现:

sizeof(ChildDerived) = 24 bytes;

虚基类实例地址(0x0012FF7C) = Derived1的vbptr (0x0012FF68) + Derived1的vbptr到虚基类实例地址的偏移量(24);

虚基类实例地址(0x0012FF7C) = Derived2的vbptr (0x0012FF70) + Derived1的vbptr到虚基类实例地址的偏移量(12)

 

 

 

对象内存布局 (14)差不多,只是改动了两个继承关系。几个类的继承关系如下图,这种继承方式不是很有实际意义,在此纯粹是探究类的memory layout。

        

代码如下:

#include <iostream>

using namespace std;

 

class Base

{

public:

         int base_member;

};

 

class Derived1 : public virtual Base

{

public:

         int derived1_member;

};

 

class Derived2 : public virtual Base

{

public:

         int derived2_member;

};

 

class ChildDerived : public virtual Derived1, public virtual Derived2

{

public:

         int childderived_member;

};

 

 

int main(void)

{

         ChildDerived cd;

 

         cout << "sizeof(Base) = /t/t" << sizeof(Base) << endl;

         cout << "sizeof(Derived1) = /t" << sizeof(Derived1) << endl;

         cout << "sizeof(Derived2) = /t" << sizeof(Derived2) << endl;

         cout << "sizeof(ChildDerived) = /t" << sizeof(ChildDerived) << endl;

 

         cout << endl;

 

         cout << "ChildDerived object's vbptr = " << (int*)&cd << endl;

 

         cout << endl;

 

         cout << "Address of virtual base class table of ChildDerived object = " << (int*)(*((int*)&cd)) << endl;

         cout << "Item 1 in ChildDerived's virtual base class table = " << *((int*)*(int*)(&cd) + 0) << endl;

         cout << "Item 2 in ChildDerived's virtual base class table = " << *((int*)*(int*)(&cd) + 1) << endl;

         cout << "Item 3 in ChildDerived's virtual base class table = " << *((int*)*(int*)(&cd) + 2) << endl;

         cout << "Item 4 in ChildDerived's virtual base class table = " << *((int*)*(int*)(&cd) + 3) << endl;

         cout << "Item 4 in ChildDerived's virtual base class table = " << *((int*)*(int*)(&cd) + 4) << endl;

 

         cout << endl;

 

         cout << "Address of virtual base class table of Derived1 object = " << (int*)*((int*)&cd + 3) << endl;

         cout << "Item 1 in Derived1's virtual base class table = " << *((int*)*((int*)&cd + 3) + 0) << endl;

         cout << "Item 2 in Derived1's virtual base class table = " << *((int*)*((int*)&cd + 3) + 1) << endl;

         cout << "Item 3 in Derived1's virtual base class table = " << *((int*)*((int*)&cd + 3) + 2) << endl;

 

         cout << endl;

 

         cout << "Address of virtual base class table of Derived2 object = " << (int*)*((int*)&cd + 5) << endl;

         cout << "Item 1 in Derived2's virtual base class table = " << *((int*)*((int*)&cd + 5) + 0) << endl;

         cout << "Item 2 in Derived2's virtual base class table = " << *((int*)*((int*)&cd + 5) + 1) << endl;

         cout << "Item 3 in Derived2's virtual base class table = " << *((int*)*((int*)&cd + 5) + 2) << endl;

 

         cout << endl;

 

         cout << "The address of base class Derived1's instance = " << (Derived1*)&cd << endl;

         cout << "The address of base class Derived2's instance = " << (Derived2*)&cd << endl;

         cout << "The address of base class Base's instance = /t" << (Base*)&cd << endl;

 

         return 0;

}

注意:因为可能出现负数,所以在上面的程序中,将原来的unsigned long* 改为了int*。运行结果:

 

图解如下:

 

 

 

下面讨论虚基类和虚函数同时存在的时候,对对象内存布局的影响。

假定各个类之间的关系如下图:

  

Base中声明了一个虚函数vfBase()和一个整形成员变量;

Derived1 override了Base中声明的虚函数vfBase(),声明了一个虚函数vfDerived1(),另有一个整形成员变量derived1_member;

Derived2 override了Base中声明的虚函数vfBase(),声明了一个虚函数vfDerived2(),另有一个整形成员变量derived2_member;

ChildDerived分别override了Base、Derived1和Derived2中声明的虚函数vfBase()、vfDerived1() 和vfDerived2(),另外有一个整形成员变量childderived_member;

 

代码如下:

#include <iostream>

using namespace std;

 

typedef void (*VFun)(void);

 

template<typename T>

VFun virtualFunctionPointer(T* b, int i)

{

         return (VFun)(*((int*)(*(int*)b) + i));

}

 

template<typename T>

int virtualBaseTableOffset(T* b, int i)

{

         return (int)*((int*)*(int*)b + i);

}

 

class Base

{

public:

         int base_member;

         inline virtual void vfBase()

         {

                   cout << "This is in Base::vfBase()" << endl;

         }

};

 

class Derived1 : public virtual Base

{

public:

         int derived1_member;

         inline void vfBase()

         {

                   cout << "This is in Derived1::vfBase()" << endl;

         }

         inline virtual void vfDerived1()

         {

                   cout << "This is in Derived1::vfDerived1()" << endl;

         }

};

 

class Derived2 : public virtual Base

{

public:

         int derived2_member;

         inline void vfBase()

         {

                   cout << "This is in Derived2::vfBase()" << endl;

         }

         inline virtual void vfDerived2()

         {

                   cout << "This is in Derived2::vfDerived2()" << endl;

         }

};

 

class ChildDerived : public Derived1, public Derived2

{

public:

         int childderived_member;

         inline void vfBase()

         {

                   cout << "This is in ChildDerived::vfBase()" << endl;

         }

         inline void vfDerived1()

         {

                   cout << "This is in ChildDerived::vfDerived1()" << endl;

         }

         inline void vfDerived2()

         {

                   cout << "This is in ChildDerived::vfDerived2()" << endl;

         }

};

 

int main(void)

{

         ChildDerived cd;

         VFun pVF;

         int* tmp;

 

         cout << "sizeof(Base) = /t/t" << sizeof(Base) << endl;

         cout << "sizeof(Derived1) = /t" << sizeof(Derived1) << endl;

         cout << "sizeof(Derived2) = /t" << sizeof(Derived2) << endl;

         cout << "sizeof(ChildDerived) = /t" << sizeof(ChildDerived) << endl;

        

cout << endl;

         cout << "address of ChildDerived object:" << endl;

         cout << "address = " << (int*)&cd << endl;

        

cout << endl;

         cout << "1st virtual function table: " << endl;

         pVF = virtualFunctionPointer(&cd, 0);

         pVF();

 

         cout << endl;

 

         cout << "1st virtual base table: " << endl;

         tmp = (int*)((int*)&cd + 1);

         cout << "address = " << tmp << endl;

         cout << virtualBaseTableOffset(tmp, 0) << "/t<- not sure yet, but it doesn't matter here." << endl;

         cout << virtualBaseTableOffset(tmp, 1) << "/t<- offset from Derived1 subobject's vbptr to Base subobject." << endl;

         cout << virtualBaseTableOffset(tmp, 2) << "/t<- means the end of this virtual base table."  << endl;

 

         cout << endl;

 

         tmp = ((int*)&cd) + 3;

         cout << "2nd virtual function table: " << endl;

         pVF = virtualFunctionPointer(tmp, 0);

         pVF();

 

         cout << endl;

 

         cout << "2nd virtual base table: " << endl;

         tmp = (int*)((int*)&cd + 4);

         cout << "address = " << tmp << endl;

         cout << virtualBaseTableOffset(tmp, 0) << "/t<- not sure yet, but it doesn't matter here." << endl;

         cout << virtualBaseTableOffset(tmp, 1) << "/t<- offset from Derived2 subobject's vbptr to Base subobject."  << endl;

         cout << virtualBaseTableOffset(tmp, 2) << "/t<- means the end of this virtual base table."  << endl;

 

         cout << endl;

 

         tmp = ((int*)&cd) + 7;

         cout << "3rd virtual function table: " << endl;

         pVF = virtualFunctionPointer(tmp, 0);

         pVF();

 

         cout << endl;

 

         cout << "Derived1 subobject address = /t" << (Derived1*)&cd << endl;

         cout << "Derived2 subobject address = /t" << (Derived2*)&cd << endl;

         cout << "Base subobject address = /t" << (Base*)&cd << endl;

         return 0;

}

运行结果如下:

 

 

ChildDerived、Derived1和Derived2对象memory layout分别图解如下:

 

两个虚基类偏移量图解如下(虚函数表和虚基类表略):

 

 

结论:

其一,只要涉及到虚基类,一切问题就变得复杂起来;

其二,如果同时存在vfptr和vbptr,vfptr居前,vbptr居后;

其三,普通基类居前,虚基类总是尽可能地排列在layout的最后;

其四,两个同一层次的虚基类subobject,先声明者居前,后声明者居后,这点和普通基类是一样的;

其五,两个不同层次的虚基类subobject,层次高者居前,层次低者居后;

其六,Stan Lippman建议,不要在一个virtual base class中声明nonstatic data member,理由是这样做会是问题变得非常复杂。 

posted @ 2013-10-27 22:08  0弓虽  阅读(194)  评论(0编辑  收藏  举报