深入理解C++对象模型-对象的内存布局,vptr,vtable
vtpr的位置:
???????為了支持多態(tài),C++引入了vtpr和vtable這兩個概念.對于每個有虛函數(shù)的類,C++都會為其生成一個vtable,并在類中添加一個隱含的數(shù)據(jù)成員vptr. 對于vptr在對象中的位置,跟類的數(shù)據(jù)成員的布局一樣,C++標準里面并沒有做出任何的規(guī)定.但是對于特定的編譯器,我們還是可以通過研究C++對象的內(nèi)存布局來確定vtpr到底是放在哪里.
????? 下面我們通過分析C++對象的內(nèi)存布局,來尋找vptr的位置.在開始討論之前我們先提供一個模板函數(shù)void PrintLayout(T const & obj),該函數(shù)用于打印obj所在內(nèi)存的內(nèi)容,下面是該函數(shù)的實現(xiàn):
PrintLayout.hxx#pragma once #include <iostream> #include <iomanip> #include <ReinterpretCast.hxx>template<typename T> void PrintLayout(T const & obj) {int * pObj = ReinterpretCast<int*>(&obj);for (int i =0; i<sizeof(obj)/sizeof(int);++i){std::cout<<std::setw(10)<< pObj[i]<<std::endl;} }
接下來通過代碼來分析一下在C++里,在沒有繼承,單繼承,多繼承以及虛繼承等情況下對象的內(nèi)存布局,下面是示例代碼,為了減少代碼量,我們將類的所有數(shù)據(jù)成員設(shè)為public的,在這里我們用struct來代替class:
//main.cpp#include <iostream> #include <PrintLayout.hxx> #include <typeinfo> using namespace std;struct NoVirtualMemFunc {int Func1(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int Func2(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; }; struct Base1 {virtual int Base1Func1(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}virtual int Base1Func2(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; }; struct Base2 {virtual int Base2Func1(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}virtual int Base2Func2(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; };struct D1:public Base1 {virtual int D1Func(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; }; struct D:public Base1,public Base2 {virtual int DFunc(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; };struct VD1:public virtual Base1 {virtual int VD1Func(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; }; struct VD2:public virtual Base1 {virtual int D2Func(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; }; struct VD:public VD1,public VD2 {int m_iData; };template<typename T> void PRINT_LAYOUT(T const & obj) {cout<<"The layout of "<<typeid(obj).name()<<"----------------"<<endl;PrintLayout(obj);cout<<endl; } int main(int argc, char* argv[]) {//沒有繼承,沒有虛函數(shù)的情況{NoVirtualMemFunc obj;obj.m_iData = 100;PRINT_LAYOUT(obj);}//沒有繼承,有虛函數(shù)的情況{Base1 obj;obj.m_iData = 100;PRINT_LAYOUT(obj);}//單繼承{D1 obj;obj.Base1::m_iData = 100;obj.m_iData = 200;PRINT_LAYOUT(obj);}//多繼承{D obj;obj.Base1::m_iData = 100;obj.Base2::m_iData = 200;obj.m_iData = 300;PRINT_LAYOUT(obj);}//虛擬繼承{VD1 obj;obj.Base1::m_iData = 100;obj.m_iData = 200;PRINT_LAYOUT(obj);}//棱形繼承{VD obj;obj.Base1::m_iData = 100;obj.VD1::m_iData = 200;obj.VD2::m_iData = 300;obj.m_iData = 500;PRINT_LAYOUT(obj);}return 0; }//輸出 /* The layout of struct NoVirtualMemFunc----------------100The layout of struct Base1----------------4294656100The layout of struct D1----------------4294740100200The layout of struct D----------------42948001004294776200300The layout of struct VD1----------------429487642948882004294864100The layout of struct VD----------------42949444294968200429493242949523005004294920100請按任意鍵繼續(xù). . .
對于有虛表的函數(shù),從上面的輸出我們可以得到以下結(jié)論,
1.沒有繼承情況,vptr存放在對象的開始位置,以下是Base1的內(nèi)存布局
| vptr : 4294656 |
| m_iData :100 |
?2.單繼承的情況下,對象只有一個vptr,它存放在對象的開始位置,派生類子對象在父類子對象的最后面,以下是D1的內(nèi)存布局
| vptr : 4294740 |
| B1:: m_iData : 100 |
| B2:: m_iData :200 |
3.多繼承情況下,對象會為每個有虛函數(shù)的父類子對象提供一個vptr,派生類子對象在所有父類子對象的最后面,所有父類子對象按照聲明順序排列,以下是D的內(nèi)存布局
| B1::vptr : 4294800 |
| B1::m_iData :100 |
| B2::vptr : 4294776 |
| B2::m_iData :200 |
| D::m_iData :300 |
4. 虛擬繼承情況下,虛父類子對象會放在派生類子對象之后,派生類子對象的第一個位置存放著一個vptr,虛擬子類子對象也會保存一個vptr,以下是VD1的內(nèi)存布局
| VD1::vptr :4294876 |
| ?Unknown : 4294888 |
| VD1::m_iData : 200 |
| B1::vptr :4294864 |
| B1::m_iData :100 |
5. 棱形繼承的情況下,非虛基類子對象在派生類子對象前面,并按照聲明順序排列,虛基類子對象在派生類子對象后面
| VD1::vptr :????????4294944 |
| VD1::Unknown : 4294968 |
| VD1::m_iData :? 200 |
| VD2::vptr :????4?? 294932 |
| VD2::Unknown : 4294952 |
| VD2::m_iData :?300 |
| VD::m_iData :?500 |
| B1::vptr :???????4294920 |
| B1::m_iData :??100 |
接下來我們將通過代碼來驗證前面結(jié)論的準確性.下面的代碼具有一定的局限性.在調(diào)試以下代碼的時候,對虛擬繼承遇到了以下幾個讓我迷惑的問題:
1.對于虛擬繼承,函數(shù)指針的大小為12
2.用vtable里面的指針調(diào)用,this不能正確傳進去
3.如果派生類的虛擬函數(shù)多于1個,則會crash
?
//main.cpp#include <iostream> #include <GetVptr.hxx> #include <typeinfo> using namespace std;struct NoVirtualMemFunc {int Func1(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int Func2(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; }; struct Base1 {virtual int Base1Func1(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}virtual int Base1Func2(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; }; struct Base2 {virtual int Base2Func1(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}virtual int Base2Func2(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; };struct D1:public Base1 {virtual int D1Func(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; }; struct D:public Base1,public Base2 {virtual int DFunc(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; };struct VD1:public virtual Base1 {virtual int VD1Func(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; }; struct VD2:public virtual Base1 {virtual int D2Func(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData; }; struct VD:public VD1,public VD2 {int m_iData; };template<class T> struct MemFuncT {typedef int (T::* T_MemFuncT)(int,int);typedef int (T::* T_MemDataT); }; template<class C> void CallMemFunc(int iFuncNum,int (C::**vptr)(int,int),C& obj,int a =500,int b =600) {for (int i =0;i<iFuncNum;++i){//cout<<ReinterpretCast<void*>(vptr[i])<<" ";(obj.*vptr[i])(a,b);}cout<<endl; } int main(int argc, char* argv[]) {//沒有繼承,有虛函數(shù)的情況{cout<<"//沒有繼承,有虛函數(shù)的情況"<<endl;Base1 obj;obj.m_iData = 100;MemFuncT<Base1>::T_MemFuncT * vptr = ReinterpretCast<MemFuncT<Base1>::T_MemFuncT *>(GetVptr(obj));CallMemFunc(2,vptr,obj);}//單繼承{cout<<"//單繼承"<<endl;D1 obj;obj.Base1::m_iData = 100;obj.m_iData = 200;MemFuncT<D1>::T_MemFuncT * vptr = ReinterpretCast<MemFuncT<D1>::T_MemFuncT *>(GetVptr(obj));CallMemFunc(3,vptr,obj);}//多繼承{cout<<"//多繼承"<<endl;D obj;obj.Base1::m_iData = 100;obj.Base2::m_iData = 200;obj.m_iData = 300;Base1 &objB1 = obj;MemFuncT<Base1>::T_MemFuncT * vptr = ReinterpretCast<MemFuncT<Base1>::T_MemFuncT *>(GetVptr(obj));CallMemFunc(3,vptr,objB1);Base2 &objB2 = obj;MemFuncT<Base2>::T_MemFuncT * vptrB2 = ReinterpretCast<MemFuncT<Base2>::T_MemFuncT *>(GetVptr(objB2));CallMemFunc(2,vptrB2,objB2);}#if 1//虛擬繼承{cout<<"//虛擬繼承"<<endl;VD1 obj;obj.Base1::m_iData = 100;obj.m_iData = 200;MemFuncT<VD1>::T_MemFuncT * vptr = ReinterpretCast<MemFuncT<VD1>::T_MemFuncT *>(GetVptr(obj));CallMemFunc(1,vptr,obj);Base1 & objB1 =obj ;MemFuncT<Base1>::T_MemFuncT * vptrB1 = ReinterpretCast<MemFuncT<Base1>::T_MemFuncT *>(GetVptr(objB1));CallMemFunc(2,vptrB1,objB1);}//棱形繼承{cout<<"//棱形繼承"<<endl;VD obj;obj.Base1::m_iData = 100;obj.VD1::m_iData = 200;obj.VD2::m_iData = 300;obj.m_iData = 500;Base1 & objB1 = obj;MemFuncT<Base1>::T_MemFuncT * vptrB1 = ReinterpretCast<MemFuncT<Base1>::T_MemFuncT *>(GetVptr(objB1));CallMemFunc(2,vptrB1,objB1);VD1 & objVD1 =obj;MemFuncT<VD1>::T_MemFuncT * vptrVD1 = ReinterpretCast<MemFuncT<VD1>::T_MemFuncT *>(GetVptr(objVD1));CallMemFunc(1,vptrVD1,objVD1);VD2 & objVD2 =obj;MemFuncT<VD2>::T_MemFuncT * vptrVD2 = ReinterpretCast<MemFuncT<VD2>::T_MemFuncT *>(GetVptr(objVD2));//CallMemFunc(1,vptrVD2,objVD2);} #endifreturn 0; }//輸出 /* //沒有繼承,有虛函數(shù)的情況 Base1::Base1Func1 m_iData=100 a=500 b=600 Base1::Base1Func2 m_iData=100 a=500 b=600//單繼承 Base1::Base1Func1 m_iData=100 a=500 b=600 Base1::Base1Func2 m_iData=100 a=500 b=600 D1::D1Func m_iData=200 a=500 b=600//多繼承 Base1::Base1Func1 m_iData=100 a=500 b=600 Base1::Base1Func2 m_iData=100 a=500 b=600 D::DFunc m_iData=300 a=500 b=600Base2::Base2Func1 m_iData=200 a=500 b=600 Base2::Base2Func2 m_iData=200 a=500 b=600//虛擬繼承 VD1::VD1Func m_iData=4294960 a=500 b=600Base1::Base1Func1 m_iData=100 a=500 b=600 Base1::Base1Func2 m_iData=100 a=500 b=600//棱形繼承 Base1::Base1Func1 m_iData=100 a=500 b=600 Base1::Base1Func2 m_iData=100 a=500 b=600VD1::VD1Func m_iData=4295032 a=500 b=600請按任意鍵繼續(xù). . . */
總結(jié)
以上是生活随笔為你收集整理的深入理解C++对象模型-对象的内存布局,vptr,vtable的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAAS(Java 认证和授权服务)
- 下一篇: 基于UVC协议的摄像头知识学习