C++类对象内存模型与成员函数调用分析(下)
来源:互联网 发布:乐乎lofter包含帅哥 编辑:程序博客网 时间:2024/06/02 07:01
C++类对象内存模型与成员函数调用分析(下)
(文章转载于 http://blog.csdn.net/fairyroad/article/details/6376646)
2.4.2 多重继承下的虚拟函数
多重继承下的虚拟函数主要有一下几个麻烦:
- 几个父类都声明了相同原型的virtual函数;
- 有不止一个父类将其析构函数声明为虚拟;
- 一般的虚拟函数问题;
先给出代码段9。
class Parent1{public: Parent1() : data_parent1(0.0){} virtual ~Parent1(){cout<<"Parent1::~Parent1()"<<endl;} virtual void speakClearly(){cout<<"Parent1::speakClearly()"<<endl;} virtual Parent1* clone() const{cout<<"Parent1::clone()"<<endl; returnnull;}protected: int data_parent1;}; class Parent2{public: Parent2() : data_parent2(1.0){} virtual ~Parent2(){cout<<"Parent2::~Parent2()"<<endl;} virtual void mumble(){cout<<"Parent2::mumble()"<<endl;} virtual Parent2* clone() const{cout<<"Parent2::clone()"<<endl; returnnull;}protected: int data_parent2;}; class Child : public Parent1, public Parent2{public: Child() : data_child(2.0){} virtual ~Child(){cout<<"Child::~Child()"<<endl;} virtual Child* clone() const{cout<<"Child::clone()"<<endl; return null;}protected: int data_child;};
就内存布局而言,有了前面的基础了,猜得出来大概是个什么样子了。好吧,我们就先猜一把,然后再写段代码验证验证。对于数据成员,多重继承使用的就是各自分配一段空间“叠放”在一起,如之前的图4所示。对于虚拟函数,其实就是多了个vptr嘛,也放进去不久结了吗?
嗯,所以我们可以猜想了,见图8。
接下来就是调试验证了,调试代码段10如下:
typedef void(*Fun)(void); int main(){ Child c; Fun pFun; int** pVtbl = (int**)&c; cout << "[0] Parent1::_vptr->" << endl; pFun = (Fun)pVtbl[0][0]; cout << " [0] "; pFun(); pFun = (Fun)pVtbl[0][1]; cout << " [1] "; pFun(); cout << " Parent1.data_parent1 = " << (int)pVtbl[1] << endl; int s = sizeof(Parent1)/4; cout << "[" << s << "] Parent2::_vptr->"<<endl; pFun = (Fun)pVtbl[s][0]; cout << " [0] "; pFun(); pFun = (Fun)pVtbl[s][1]; cout << " [1] "; pFun(); s++; cout << " Parent2.data_parent2 = " << (int)pVtbl[s] << endl; s++; cout << "[3] Child.data_child = " << (int)pVtbl[s] << endl;}
需要的是,这段代码的运行要将虚析构函数注释掉,理由应该很好理解吧,对象都被析构掉了,指针也就成为悬挂指针了,SIGSEGV就会触发。Code::Blocks(GCC 4.5.2)下运行结果如下:
再次说明一下,因为我们注释掉了虚析构函数那一行,所以上面的输出中没有Child::~Child()之类的信息。所以,我们的猜想图8是正确的。
根据《Inside The C++ Object Model》一书,关于多重继承主要有三种情况要仔细考虑,对着图8,这三种情况其实都是浮云。
通过一个“指向第二个父类,如Parent2”的指针,调用子类的虚拟函数。请看代码段11:
Parent2* pP2 = new Child; // 下面的代码将调用Child::~Child() // 因此pP2必须被向后调整sizeof(Parent1)个bytes,由编译器和运行期信息参与完成 delete pP2;
还是回到图8,注意到一个问题,Parent1和Child指针所指向的位置是一样的(如果都是取的同一个Child对象的地址),但是Parent2不是,它与Parent1和Child的指针之间存在一个偏移量,看下面的代码就知道了:
Child c;Parent1* pP1 = &c;Parent2* pP2 = &c;Child* pC = &c;cout<<pP1<<"/n"<<pC<<"/n"<<pP2<<"/n/n";
运行结果如下图:
pP1与pC内容一样,pP2与pP1和pC之间存在8个字节的偏移量(8个字节是由一个4字节int变量和一个4字节指针引起的)。
对于代码段11,因为pP2指向Child对象中Parent2子对象处,为了能够正确执行,pP2必须调整到Child对象起始处。
- 通过一个“指向Child类”的指针,调用Parent2中一个继承而来的虚拟函数。在这种情况下,子类指针必须再次被调整,以指向第二个父类Parent2处。例如:
Child* pC = new Child;// 调用Parent2::mumble()// pC必须被向前调整sizeof(Parent1)个bytes,由编译器和运行期信息参与完成pC->mumble();
- 第三种情况发生在一个语言扩充性质之下:允许一个虚拟函数的返回值类型有所变化(注意,返回值类型不是激活C++重载机制的充分条件),可能是父类类型,也可能是子类类型,这一点通过clone()函数来描述,看下面代码:
Parent2* pP1 = new Child;// 调用Child* Child::clone()// 返回值必须被调整,以指向Parent2子对象,由编译器和运行期信息参与完成Parent2* pP2 = pP1->clone();
当进行pP1->clone()时,pP1会被调整到指向Child对象的起始地址,于是clone的Child版会被调用,它会传回一个指针,指向一个新的Child对象,该对象的地址在被指定给pP2之前,必须经过调整,以指向Parent2子对象处。
之前的注释中都有一句话,“XXX必须被调整,以指向Parent2子对象,由编译器和运行期信息参与完成”,确实,就是编译器会去做的事情,我们也不用管,因为这也是compiler-dependent的。
2.4.3 虚继承下的虚拟函数
虚拟继承的出现就是为了解决重复继承中多个间接父类的问题的,经典继承结构图就是环形继承链:
class PP {……};class P1 : virtual public PP{……};class P2: virtual public PP{……};class C : public P1, public P2{…… };
虚拟继承是个有点麻烦乃至无聊的东西,实践中不被推荐,一般来说:
- 先是P1,然后是P2,接着是C,而PP这个超类都放在最后的位置;
- 各个类内部的布局与多重继承一样;
3、C++对象模型总结
大道至简,如果理解了前面的文字,下面四句话应该就差不多了:
- 非静态数据成员都存放在对象所跨有的地址空间中,静态数据成员则存放于对象所跨有的地址空间之外;
- 非虚拟成员函数(静态和非静态)也存放于对象所跨有的地址空间之外,且编译器将其改写为普通的非成员函数的形式(以求降低调用开销);
- 对于虚拟成员函数,则借助vtbl和vptr支持。
- 对于继承关系,子类对象跨有的地址空间中包含了父类对象的实体,通过嵌入type-info信息进行识别和虚函数调用。
4、参考资料
- Inside The C++ Object Model,Lippaman,第1、2、4章。
- C++对象内存布局,陈皓专栏, http://blog.csdn.net/haoel/archive/2008/10/15/3081328.aspx
- C++类对象内存模型与成员函数调用分析(下)
- C++类对象内存模型与成员函数调用分析(下)
- C++类对象内存模型与成员函数调用分析(下)
- C++类对象内存模型与成员函数调用分析(下)
- C++类对象内存模型与成员函数调用分析(下)
- C++类对象内存模型与成员函数调用分析(下)
- C++类对象内存模型与成员函数调用分析(下)
- C++类对象内存模型与成员函数调用分析
- C++类对象内存模型与成员函数调用分析
- C++类对象内存模型与成员函数调用分析(上)
- C++类对象内存模型与成员函数调用分析(中)
- C++类对象内存模型与成员函数调用分析(上)
- C++类对象内存模型与成员函数调用分析(中)
- C++类对象内存模型与成员函数调用分析(上)
- C++类对象内存模型与成员函数调用分析(中)
- C++类对象内存模型与成员函数调用分析(上)
- C++类对象内存模型与成员函数调用分析(中)
- C++类对象内存模型与成员函数调用分析(上)
- Eclipse调试Android工具集锦之一:虚拟机
- 重生
- VC之中自己写了一个类,但在ClassWizard中的类列表中找不到,怎么办?求指导。。
- 领域模型vs E-R模型
- joj1197
- C++类对象内存模型与成员函数调用分析(下)
- 谈权限设计问题
- Eclipse调试Android工具集锦之二-DDMS
- Extjs 动态树 异步树
- python知识积累(五)——编写模块
- 今晚计划
- epoll 问题
- UNIX/LINUX编程学习之文件共享
- android 环境搭配 win7环境下