跟我一起学习C++虚函数--第四篇
在前一篇,我们讨论了在多继承情况下,具有虚函数的类的内存布局情况。本篇将进一步探索在多重继承,即具有重复继承的情况下的内存布局情况。在阅读本篇和下一篇之前,建议先阅读本博客另一篇博文《浅析GCC下C++多重继承 & 虚拟继承的对象内存布局》。
先说一点题外话,细心的读者可能会发现,我们在探索不同情况下类的内存布局时,总是先通过查看类的大小以及其中各个成员变量的地址来进行分析,然后再具体定位某一位置的值。从最原始的内存中的对象分布,我们可以获得最深入最有效的理解。
OK,请看例子:
1 #include <iostream> 2 using namespace std; 3 4 class Top 5 { 6 public: 7 virtual void x(){cout << "top x" << endl;} 8 virtual void print0(){cout << "top print" << endl;} 9 public: 10 int a; 11 }; 12 13 class Left:public Top 14 { 15 public: 16 virtual void y(){cout << "left y" << endl;} 17 virtual void print1(){cout << "left print" << endl;} 18 public: 19 int b; 20 }; 21 22 class Right:public Top 23 { 24 public: 25 virtual void z(){cout << "right z" << endl;} 26 virtual void print2(){cout << "right print" << endl;} 27 public: 28 int c; 29 }; 30 31 class Bottom : public Left, public Right 32 { 33 public: 34 virtual void y(){cout << "bottom y" << endl;} 35 virtual void z(){cout << "bottom z" << endl;} 36 virtual void print3(){cout << "bottom print" << endl;} 37 public: 38 int d; 39 }; 40 41 int main() 42 { 43 /*first part*/ 44 cout << sizeof(Top) << "\t" << sizeof(Left) << "\t" << sizeof(Right) << "\t" << sizeof(Bottom) << endl; 45 //输出:8 12 12 28 46 Bottom *b = new Bottom(); 47 cout << b << " " << &b->Left::a << " " << &b->b << " " << &b->Right::a << " " << &b->c << " " << &b->d << endl; 48 //输出:0x8c0f008 0x8c0f00c 0x8c0f010 0x8c0f018 0x8c0f01c 0x8c0f020 49 50 /*second part*/ 51 typedef void (*Func)(void); 52 Func pFunc; 53 pFunc = (Func)*((int *)*(int *)(b)); 54 pFunc();//输出:top x 55 pFunc = (Func)*((int *)*(int *)(b)+1); 56 pFunc();//输出:top print 57 pFunc = (Func)*((int *)*(int *)(b)+2); 58 pFunc();//输出:bottom y 59 pFunc = (Func)*((int *)*(int *)(b)+3); 60 pFunc();//输出:left print 61 pFunc = (Func)*((int *)*(int *)(b)+4); 62 pFunc();//输出:bottom z 63 pFunc = (Func)*((int *)*(int *)(b)+5); 64 pFunc();//输出:bottom print 65 //pFunc = (Func)*((int *)*(int *)(b)+6); 66 //pFunc();//段错误 67 68 /*third part*/ 69 pFunc = (Func)*((int *)*((int *)(b)+3)); 70 pFunc();//输出:top x 71 pFunc = (Func)*((int *)*((int *)(b)+3)+1); 72 pFunc();//输出:top print 73 pFunc = (Func)*((int *)*((int *)(b)+3)+2); 74 pFunc();//输出:bottom z 75 pFunc = (Func)*((int *)*((int *)(b)+3)+3); 76 pFunc();//输出:right print 77 //pFunc = (Func)*((int *)*((int *)(b)+3)+4); 78 //pFunc();//段错误 79 80 delete b; 81 return 0; 82 }
对于上面的例子,我们分为三部分进行讲解。
第一部分:多重继承情况下,对象本身(除虚函数表外)的内存布局。
从代码中first part的输出情况来看,Top、Left和Right的大小很容易理解,至于Bottom类的大小,如果你看过参考文献一,那么也很容易理解。Bottom类包含了两次Top类中的a成员变量,因此总共有5个int成员变量,为20字节。再加上两个虚指针,即为28字节。
从输出的Bottom对象中成员变量地址情况,我们可以用如下图来表示内存布局:
简单地说,多重继承时,子类会有多个祖父类的存在。
第二部分:多重继承情况下,主要虚函数表的内存布局。
从代码的second part的输出情况来看,我们可以用下图来表示主要虚函数表的内存布局:
第三部分:多重继承情况下,次要虚函数表的内存布局。
从代码的third part的输出情况来看,我们可以用下图来表示次要虚函数表的内存布局:
对比前一篇多继承情况下类的内存布局,我们可以发现:在多重继承情况下,不仅祖父类的成员变量在子类中会有多份存在,祖父类的虚函数同样会在子类的虚函数表中有多份存在,分别位于主要虚函数表和次要虚函数表中。
参考文献:
1.《浅析GCC下C++多重继承 & 虚拟继承的对象内存布局》
2. 《深度探索C++对象模型》