vtbl和vptr
下面简单图示说明使用gcc 4.6在x86 ubuntu上vptr和vtbl(以及VTT)布局。
关于VTT的参考链接:
http://stackoverflow.com/questions/6258559/what-is-the-vtt-for-a-class
根据我自己的验证,得到下面的结论:
- x86 ubuntu上char类型为1字节,int为4个字节,double为8个字节,地址对齐方式是4字节对齐
2)如果一个类没有父类,并且没有虚函数,那么其对象的内存布局就是各个数据成员按定义顺序排列,并对其大小进行4字节对齐后得到的结果。没有vtbl,因而也没有vptr.
3)如果一个类没有父类,并且有虚函数,其对象开始地址会包含一个指向本类型vtbl中第一个虚函数的指针(vptr),接下来是其各个数据成员。vtbl中前面两项是0以及一个指向其typeinfo的指针,后面是虚函数的地址,而vptr指向自己vtbl中的第一个虚函数地址。
4)如果一个类是从一个父类public继承,并且父类中没有虚函数,其对象内存布局是其父类的数据成员,后面跟随的是本类的数据成员。因为父类和子类都没有虚函数,所以没有vtbl,因而没有vptr。
5)如果一个类是从一个父类public继承,并且父类中有虚函数,但是自己没有虚函数。那么其对象内存布局第一个位置是指向本类型vtbl中第一个虚函数的偏移地址的指针。其vtbl中前面两项分别是0和指向自己typeinfo的指针,第三项开始是其虚函数的偏移地址。
6)如果一个类从父类virtual继承,那么还会生成VTT表(实际上其中包含指向其vtbl偏移地址的指针)。
7)多重继承的情况下,其内存布局是从基类内存布局依次排列,然后后面跟随自己的虚函数指针(假设有虚函数)和其数据成员。
测试代码如下(主要考察是否有虚函数以及public和virtual继承的差别):
#include <iostream>
class NoVirtualFunc{
int a;
double b;
char c;
};
class VirtualFunc{
int x;
double y;
char z;
public:
virtual void vfunc1(){}
};
class PublicDerivedFromNoVirtualFunc: public NoVirtualFunc{
int pdfnvf;
};
class PublicDerivedFromVirtualFunc: public VirtualFunc{
int pdfvf;
};
class VirtualDerivedFromNoVirtualFunc: virtual NoVirtualFunc{
int vdfnvf;
};
class VirtualDerivedFromVirtualFunc: virtual VirtualFunc{
int vdfvf;
};
class PublicMultiple: public NoVirtualFunc, public VirtualFunc{
int pm;
};
class VirtualMultiple: virtual NoVirtualFunc, virtual VirtualFunc{
int vm;
};
class AnotherVirtualFunc{
char c;
public:
virtual void g(){}
};
class PublicMultipleTwoVirtual:public VirtualFunc, public AnotherVirtualFunc{
char pmtv;
};
class VirutalMultipleTwoVirtual:virtual VirtualFunc, virtual AnotherVirtualFunc{
char vmtv;
};
int main(void)
{
NoVirtualFunc o1;
VirtualFunc o2;
PublicDerivedFromNoVirtualFunc o3;
PublicDerivedFromVirtualFunc o4;
VirtualDerivedFromNoVirtualFunc o5;
VirtualDerivedFromVirtualFunc o6;
PublicMultiple o7;
VirtualMultiple o8;
AnotherVirtualFunc o9;
PublicMultipleTwoVirtual o10;
VirutalMultipleTwoVirtual o11;
return 0;
}
x86 ubuntu下, char、int、double分别占用1、4、8个字节,并且默认4字节对齐。可以使用下面命令打印出vtbl和vtt信息:
g++ -fdump-class-hierarchy base_derived.cpp
然后结合gdb打印各个字段的地址以及相关的指针地址,所画内存布局如下:
在多重继承的情况下,如果多重继承的父类是同一个类,并且不是使用virtual继承,那么最终的子类中会存在顶端类内容的多份拷贝。
看下面代码:
class A {
int x;
public:
virtual void f(){}
};
class B: public A
{
int y;
};
class C: public A
{
int z;
};
class D: public B, public C
{
int r;
};
int main(void)
{
A a;
B b;
C c;
D d;
return 0;
}
其内存布局如下:
但是如果采用virtual继承,那么祖先类数据在孙子类中只有一个一份。
代码如下:
class A {
int x;
public:
virtual void f(){}
};
class B: public virtual A
{
int y;
};
class C: public virtual A
{
int z;
};
class D: public B, public C
{
int r;
};
int main(void)
{
A a;
B b;
C c;
D d;
return 0;
}
内存布局如下:
this指针
c++对象都有一个this指针指向当前对象,讲义中侯捷老师讲解了MFC中的代码例子,如下图所示: