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,理由是这样做会是问题变得非常复杂。