在上篇文章《戳我》中,简单说了继承的三种分类:单继承、多重继承、重复继承。
一般的如果没有虚函数,那对象的内存布局就如我们看到的一样,定义了几个变量根据字节对齐就能算出其在内存中所占字节大小。但是有了虚函数,就不同了,因为有了虚函数就意味着存在虚函数指针,那指针我们知道是占四个字节(32位)的,所以从本篇文章开始来分析存在虚函数时对象的内存布局是怎么样的?(版本是Visual Studio2012)
对于单继承:
写出以下代码:
class Father { public: Father(int data1):fa(data1){} virtual void f(){cout<<"Father::f()"<<" ";} virtual void ff(){cout<<"Father::ff()"<<" ";} virtual void fff(){cout<<"Father::fff()"<<" ";} protected: int fa; }; class Son:public Father { public: Son(int data):sa(data),Father(data){} virtual void f(){cout<<"Son::f()"<<" ";} virtual void ss(){cout<<"Son::ss()"<<" ";} virtual void sss(){cout<<"Son::sss()"<<" ";} protected: int sa; }; class Grandson:public Son { public: Grandson(int data):ga(data),Son(data){} virtual void f(){cout<<"Grandson::f()"<<" ";} virtual void ss(){cout<<"Grandson::ss()"<<" ";} virtual void ggg(){cout<<"Grandson::ggg()"<<" ";} protected: int ga; }; typedef void (*Function)(void);//函数指针 int main() { //main函数主要进行打印 Grandson *pgs=new Grandson(12); Function pFun=NULL;//定义一个函数指针 cout<<"[0] _vfptr"<<endl; for(int i=0;i<6;++i) { pFun=((Function*)(long**)(*(long*)(pgs)))[i]; cout<<" "<<"["<<i<<"]"<<" "; pFun(); cout<<((long**)(*(long*)pgs))[i]<<endl; } cout<<"[1] "<<((long*)pgs)[1]<<endl; cout<<"[2] "<<((long*)pgs)[2]<<endl; cout<<"[3] "<<((long*)pgs)[3]<<endl; delete pgs; return 0; }
注: (long*)(pgs):将pgs强转成long* (*(long*)(pgs)):解引用,虚函数表的地址 (long**)(*(long*)(pgs)):虚函数表的地址强转成二级指针
查看Grandson类生成对象的内存布局:(点击【项目】-》【属性】-》【C/C++】-》【命令行】-》在其它选项处写上
【/d1 reportSingleClassLayoutGrandson】-》点击【确定】就好了 )
可以看出:
1、子类继承了父类的成员变量,成员函数不占空间。
2、有虚函数时出现了一个vfptr指针,并把该指针放在了内存的开始处(相对对象偏移为0)。
3、同名的函数在虚表中被覆盖。
4、同时也看到了有一张虚表的存在,虚表中从0号下标开始就存放的是虚函数,有一个问题就是从虚表中也能看出有RTTI信息(&Grandson_meta)和vfptr相对于类型的偏移信息(0),但它不在0号下标锁对应的位置,那它在哪?怎么打印出来?看到的宝宝希望能帮我解答。
将上面程序进行打印及调试,可以看到:
现在,我们来画出单继承下Grandson类的内存布局: