C++虚继承的内存模型
来源:互联网 发布:mac python如何加载库 编辑:程序博客网 时间:2024/04/29 18:48
#include <iostream> using namespace std; class ZooAnimal{public: virtual void print() { cout << "run: ZooAnimal::print()" <<endl; } int a;}; class Bear: public ZooAnimal{public: void run(int whichTable, int whichFunc) { //whichTable是个偏移量,用来在对象内存中找到vfptr,即找虚函数表 void (***func)(void)=reinterpret_cast<void (***)(void)>(this); (*(func+whichTable))[whichFunc]();//运行哪个表中的哪个函数 } virtual void dis() { cout << "run: Bear::dis()" <<endl; } void dis(char) { cout << "run: Bear::dis(char)" <<endl; } virtual void dis(int) { cout << "run: Bear::dis(int)" <<endl; } int b;}; int main(){ Bear b; ZooAnimal& a=static_cast<ZooAnimal&>(b); cout << "ZooAnimal:" <<endl; cout << "&a=" << &a <<endl; cout << "&a.a=" << &a.a <<endl; cout<<endl; a.run(0,0); a.run(0,1); a.run(0,2); cout<<endl; cout << "Bear:" <<endl; cout << "&b=" << &b <<endl; cout << "&b.a=" << &b.a <<endl; cout << "&b.b=" << &b.b <<endl; cout<<endl; b.run(0,0); b.run(0,1); b.run(0,2); cout<<endl; cout << "sizeof(ZooAnimal)=" << sizeof(ZooAnimal) <<endl; cout << "sizeof(Bear)=" << sizeof(Bear) << endl;}
平台Ubantu,g++ 4.8
结果:
ZooAnimal:
&a=0xbf859914
&a.a=0xbf859918
run: ZooAnimal::print()
run: Bear::dis()
run: Bear::dis(int)
Bear:
&b=0xbf859914
&b.a=0xbf859918
&b.b=0xbf85991c
run: ZooAnimal::print()
run: Bear::dis()
run: Bear::dis(int)
sizeof(ZooAnimal)=8
sizeof(Bear)=12
Bear对象,相对于Animal对象,新增了b变量。没有为Bear的新的虚函数增加新vfptr,而是将Bear的新的虚函数地址,在原来vfptr指向的虚函数表中尾增。
我们接下来让Bear虚继承自ZooAnimal,class Bear: virtual public ZooAnimal
重新编译,运行
ZooAnimal:
&a=0xbfb77688
&a.a=0xbf7768c
run: ZooAnimal::print()
段错误(核心已转储)
将main函数中a.run(0,1),a.run(0,2)注释的,编译运行:
ZooAnimal:
&a=0xbfe99ad8
&a.a=0xbfe99adc
run: ZooAnimal::print()
Bear:
&b=0xbfe99ad0
&b.a=0xbfe99adc
&b.b=0xbfe99ad4
run: Bear::dis()
run: Bear::dis(int)
段错误(核心已转储)
将main中的b.run(0,2)注释掉,编译运行:
ZooAnimal:
&a=0xbfe99ad8
&a.a=0xbfe99adc
run: ZooAnimal::print()
Bear:
&b=0xbfe99ad0
&b.a=0xbfe99adc
&b.b=0xbfe99ad4
run: Bear::dis()
run: Bear::dis(int)
sizeof(ZooAnimal)=9
sizeof(Bear)=16
通过上面的过程及最终的结果,可以推测出Bear的内存布局:
可见派生类Bear并没有使用基类ZooAnimal的vfptr,而是单独为自己开辟了一个vfptr。通过a,b的位置变化,看出ZooAnimal被放到尾部了。
继续,派生Panda。
class Panda: public Bear{public: void run(int whichTable, int whichFunc) { //whichTable是个偏移量,用来在对象内存中找到vfptr,即找虚函数表 void (***func)(void)=reinterpret_cast<void (***)(void)>(this); (*(func+whichTable))[whichFunc]();//运行哪个表中的哪个函数 } virtual void test() { cout << "run: Panda::test()" <<endl; } int t;}; int main(){ Panda b; ZooAnimal& a=static_cast<ZooAnimal&>(b); cout << "ZooAnimal:" <<endl; cout << "&a=" << &a <<endl; cout << "&a.a=" << &a.a <<endl; cout<<endl; a.run(0,0); //a.run(0,1);段错误 //a.run(0,2);段错误 //a.run(0,3);段错误 cout<<endl; Bear& c=static_cast<Bear&>(b); cout << "Bear:" <<endl; cout << "&c=" << &c <<endl; cout << "&c.a=" << &c.a <<endl; cout << "&c.b=" << &c.b <<endl; cout << endl; c.run(0,0); c.run(0,1); c.run(0,2); //c.run(0,3);段错误 cout<<endl; cout << "Panda:" <<endl; cout << "&b=" << &b <<endl; cout << "&b.a=" << &b.a <<endl; cout << "&b.b=" << &b.b <<endl; cout << "&b.t=" << &b.t <<endl; cout<<endl; b.run(0,0); b.run(0,1); b.run(0,2); //b.run(0,3);段错误 cout <<endl; cout << "sizeof(ZooAnimal)=" << sizeof(ZooAnimal) <<endl; cout << "sizeof(Bear)=" << sizeof(Bear) << endl; cout << "sizeof(Panda)=" << sizeof(Panda) << endl;}结果:
根据结果推测内存模型
虚继承的祖父类仍被放到尾部,并具有自己的独立的vfptr。而孙子类Panda非虚继承父类Bear,采取的方法也仍是只有一个vfptr,并将自己新的虚函数的地址尾加到表中。
继续,将其扩展成封闭的菱形继承:
添加了Raccon类,该类不包含虚函数,有一个成员变量int r。
class Raccon:virtual public ZooAnimal{ public: void run(int whichTable, int whichFunc) { //whichTable是个偏移量,用来在对象内存中找到vfptr,即找虚函数表 void (***func)(void)=reinterpret_cast<void (***)(void)>(this); (*(func+whichTable))[whichFunc]();//运行哪个表中的哪个函数 } int r;};
int main(){ Panda b; ZooAnimal& a=static_cast<ZooAnimal&>(b); cout << "ZooAnimal:" <<endl; cout << "&a=" << &a <<endl; cout << "&a.a=" << &a.a <<endl; cout<<endl; a.run(0,0); //a.run(0,1);//段错误 //a.run(0,2);//段错误 //a.run(0,3);//段错误 cout<<endl; Bear& c=static_cast<Bear&>(b); cout << "Bear:" <<endl; cout << "&c=" << &c <<endl; cout << "&c.a=" << &c.a <<endl; cout << "&c.b=" << &c.b <<endl; cout << endl; c.run(0,0); c.run(0,1); c.run(0,2); //c.run(0,3);//段错误 cout<<endl; Raccon& d=static_cast<Raccon&>(b); cout << "Raccon:" <<endl; cout << "&d=" << &d <<endl; cout << "&d.a=" << &d.a <<endl; cout << "&d.r=" << &d.r <<endl; cout << endl; //d.run(0,0);段错误 //d.run(0,1);段错误 //d.run(0,2);段错误 //c.run(0,3);段错误 cout<<endl; cout << "Panda:" <<endl; cout << "&b=" << &b <<endl; cout << "&b.a=" << &b.a <<endl; cout << "&b.b=" << &b.b <<endl; cout << "&b.r=" << &b.r <<endl; cout << "&b.t=" << &b.t <<endl; cout<<endl; b.run(0,0); b.run(0,1); b.run(0,2); //b.run(0,3);//段错误 cout <<endl; cout << "sizeof(ZooAnimal)=" << sizeof(ZooAnimal) <<endl; cout << "sizeof(Bear)=" << sizeof(Bear) << endl; cout << "sizeof(Raccon)=" << sizeof(Raccon) << endl; cout << "sizeof(Panda)=" << sizeof(Panda) << endl;}
Panda从直接基类Bear和Racoon分别继承了vfptr,并将自己的新的虚函数指针,尾加到了第一个直接基类Bear的vftable中。而虚继承而来的祖类ZooAnimal仍就整体放到了Panda尾部。特别注意下vfptr$Raccon。笔者实践的时候,填加了函数用于访问虚函数表中的函数指针的值,虚函数表尾部确实都为0,应该是用于标识虚函数表结束。如果对祖类ZooAnimal的继承不是虚继承,Panda的内存布局,根据上述的原则,而是很好推断的,以前从ZooAnimal虚继承而来的类,都是要自己单独再分配自己的vfptr,现在可以自己重用基类的了,则Bear将减少一个指针的内存大小,即sizeof(Bear)=12,同理sizeof(Raccon)=12,而Panda虽然不用单独分配自己的vfptr,但是由于不是虚继承,对于ZooAnimal的成员变量int a,将有两份拷贝,而已sizeof(Panda)仍是28.
在网上搜了下资料后自己有时间代码实践验证下。对比网上其他的,对于虚继承跟别人得到的结果又出入。其他人的结果大都虚继承后有vbptr,即虚基类偏移量指针。比如这篇文章:点击打开链接
笔者,将其代码写入自己的平台验证,最后孙子类的大小是28而不是文章说的36。他的孙子类出现了两个vbptr。他使用的是vs2010.
编译器实现有关?
将代码拷贝到vs2012中运行,其使用的是微软自家的cl编译器。
由于运行的时候run函数中的func函数指针为void (***)(void),在通过其运行Bear的virtual void dis(int)时,编译器出错,栈出了问题,问了方便分析,直接将该函数注释掉。根据最后的输出结果,分析得到如下内存分布:
sizeof(ZooAnimal)=8;
sizeof(Bear)=20;
sizeof(Raccon)=16;
sizeof(Panda)=32;
特别注意,没有为Raccon生成vfptr。虚基类偏移表中,第一项应该是具有一定的标识意义,猜测可能是vbptr相对于对象头部的偏移量,而第二项保存的是虚基类相对于vbptr的偏移量。Raccon类中并没有虚函数,尽管他虚继承的ZooAnimal中有虚函数,并没有为Raccon生成用于保存虚函数的vfptr。我试着给Raccon添加了一个虚函数virtual void vs2012test(),结果编译器为Rccon分配了vfptr,并且vbptr$Rccon偏移表第一个变为-4
sizeof(ZooAnimal)=8;
sizeof(Bear)=20;
sizeof(Raccon)=20;
sizeof(Panda)=36;
虚继承的内存布局与编译器相关!!
g++原则更为简单,将虚基类内存(包括vfptr)尾加到派生类。如果虚基类有虚函数,派生类一定会有自己的vfptr,如果派生类没有虚函数,该vfptr指向空。
cl虚继承时,同样将虚基类内存(包括vfptr)尾加到派生类,并且派生类将产生vbptr,其中存放了虚基类对象在派生类对象中的偏移量。派生类对象中有无vfptr与虚基类无关,仅当派生类有虚函数时,才会为其分配vfptr。
编译器是如何实现对虚基类的访问?这里暂时不继续挖了,越挖越深,洗洗睡了~
- 虚继承的内存模型分析
- 多重虚继承的内存模型分析
- C++虚继承的内存模型
- C++虚继承的内存模型
- C++ 虚继承与普通继承的内存模型对比
- C++ 虚继承与普通继承的内存模型对比
- C++-对象继承内存模型配图
- C++ 多继承和虚继承的内存布局
- 2013亚马逊面试题--虚继承的内存模型分析
- C++ 无虚函数、无虚基类的继承内存模型
- 虚继承与虚基类中对象的内存模型分析
- C++继承模型的内存布局
- 钻石型继承模型的内存分布
- c++ 继承 33 虚继承对c++ 对象内存模型造成的影响
- 二十八、继承(五) 虚继承对C++对象内存模型造成的影响
- C++对象模型:多重继承和虚继承的内存布局
- C++虚继承内存对象模型探讨
- C++虚继承内存对象模型探讨
- 快速排序总结
- 移动IM开发那些事:技术选型和常见问题
- URL中的特殊字符的作用
- contourArea函数
- Socket之广播
- C++虚继承的内存模型
- CAP理论和BASE模型
- class和struct的区别
- eclipse java中路径问题
- 【POJ2031】【最小生成树】【g++ f c++ lf】
- 【小熊刷题】Binary Tree Maximum Path Sum
- 【算法导论】分治策略
- hdu 4628 Pieces 状态压缩DP
- C++指针加整数、两个指针相减的问题