深入理解C++對象模型-對象的內存布局,vptr,vtable

vtpr的位置:
???????為了支持多態,C++引入了vtpr和vtable這兩個概念.對于每個有虛函數的類,C++都會為其生成一個vtable,并在類中添加一個隱含的數據成員vptr. 對于vptr在對象中的位置,跟類的數據成員的布局一樣,C++標準里面并沒有做出任何的規定.但是對于特定的編譯器,我們還是可以通過研究C++對象的內存布局來確定vtpr到底是放在哪里.
????? 下面我們通過分析C++對象的內存布局,來尋找vptr的位置.在開始討論之前我們先提供一個模板函數void PrintLayout(T const & obj),該函數用于打印obj所在內存的內容,下面是該函數的實現:


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++里,在沒有繼承,單繼承,多繼承以及虛繼承等情況下對象的內存布局,下面是示例代碼,為了減少代碼量,我們將類的所有數據成員設為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[])
{//沒有繼承,沒有虛函數的情況{NoVirtualMemFunc obj;obj.m_iData = 100;PRINT_LAYOUT(obj);}//沒有繼承,有虛函數的情況{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請按任意鍵繼續. . .



對于有虛表的函數,從上面的輸出我們可以得到以下結論,

1.沒有繼承情況,vptr存放在對象的開始位置,以下是Base1的內存布局

vptr : 4294656

m_iData :100


?2.單繼承的情況下,對象只有一個vptr,它存放在對象的開始位置,派生類子對象在父類子對象的最后面,以下是D1的內存布局

vptr : 4294740

B1:: m_iData : 100

B2:: m_iData :200


3.多繼承情況下,對象會為每個有虛函數的父類子對象提供一個vptr,派生類子對象在所有父類子對象的最后面,所有父類子對象按照聲明順序排列,以下是D的內存布局

B1::vptr : 4294800

B1::m_iData :100

B2::vptr : 4294776

B2::m_iData :200

D::m_iData :300


4. 虛擬繼承情況下,虛父類子對象會放在派生類子對象之后,派生類子對象的第一個位置存放著一個vptr,虛擬子類子對象也會保存一個vptr,以下是VD1的內存布局

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


接下來我們將通過代碼來驗證前面結論的準確性.下面的代碼具有一定的局限性.在調試以下代碼的時候,對虛擬繼承遇到了以下幾個讓我迷惑的問題:

1.對于虛擬繼承,函數指針的大小為12

2.用vtable里面的指針調用,this不能正確傳進去

3.如果派生類的虛擬函數多于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[])
{//沒有繼承,有虛函數的情況{cout<<"//沒有繼承,有虛函數的情況"<<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;
}//輸出
/*
//沒有繼承,有虛函數的情況
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請按任意鍵繼續. . .
*/





本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/254783.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/254783.shtml
英文地址,請注明出處:http://en.pswp.cn/news/254783.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Visual Studio Code 常用插件整理

常用插件說明&#xff1a; 一、HTML Snippets 超級使用且初級的H5代碼片段以及提示 二、HTML CSS Support 讓HTML標簽上寫class智能提示當前項目所支持的樣式 三、Debugger for Chrome 讓vscode映射chrome的debug功能&#xff0c;靜態頁面都可以用vscode來打斷點調試、配飾稍…

sublime 正則搜索日語字符

sublime 正則搜索日語字符 [\x{3041}-\x{3096}\x{30A0}-\x{30FF}\x{3400}-\x{4DB5}\x{4E00}-\x{9FCB}\x{F900}-\x{FA6A}\x{2E80}-\x{2FD5}\x{FF5F}-\x{FF9F}\x{3000}-\x{303F}\x{31F0}-\x{31FF}\x{3220}-\x{3243}\x{3280}-\x{337F}\x{FF01}-\x{FF5E}] 參考: http://www.localiz…

函數域中申請堆空間出函數后不會自動釋放

#include<iostream> using std::cout; using std::endl; char *scat(char *s1,char *s2) { char *snew char[strlen(s1)strlen(s2)1]; strcpy(s,s1); strcat(s,s2); return s;//返回剛申請的堆空間的首址&#xff0c;出了函數后變量s就不復存在了&#xff0c;但申請的…

/home文件夾重新劃分獨立分區

1. 備份home文件夾&#xff0c;數據丟失的時候可以使用cp -av /home/* /data/2. 磁盤分區2.1 虛擬機中添加磁盤并讓系統識別&#xff0c;使用# echo - - - >> /sys/class/scsi_host/host0/scan# echo - - - >> /sys/class/scsi_host/host1/scan# echo - - - >…

使用halcon將一個圓上的點擬合成圓形并且求出圓心

我們在自動化貼裝機標定過程中&#xff0c;需要計算吸頭的旋轉中心位置。我們一般使用的方法是使用模板匹配&#xff0c;做一個模板&#xff0c;吸頭旋轉一個角度尋找模板一次&#xff0c;通過多次旋轉求取吸頭的旋轉中心。 使用halcon實現 public bool FitCircle(double[] X…

This 指針

如果你期望衍生類別重新定義一個成員函數&#xff0c;那么你應該在基礎類別中把此函數設為 virtual。 以單一指令喚起不同函數&#xff0c;這種性質稱為Polymorphism&#xff0c;意思是"the ability toassume many forms"&#xff0c;也就是多態。 虛擬函…

史上最全亞歷山大大帝名言

&#xff08;1&#xff09;把財富分給他人&#xff0c;把希望留給自己&#xff0c;他將帶給我無窮的財富。——亞歷山大大帝 &#xff08;2&#xff09;山不走到我這里來&#xff0c;我就走到他那里去。——亞歷山大大帝 &#xff08;3&#xff09;能夠戰勝恐懼就能戰勝死亡。—…

jdbcmysql

做java開發難免會用到數據庫,操作數據庫也是java開發的核心技術。那我們現在就來談談javajdbc來操作mysql數據庫吧 第一步&#xff1a;我們需要把mysql的驅動引進來這里引驅動就是把mysql-connector-java-5.1.37-bin.jar加到項目中來&#xff0c;下面附jar包 第二步&#xff1a…

char **p, char a[16][8]; 問:p=a 是否會導致程序在以后出現問題?為什么?

int (*v)[10]; 在 Visual C 里面不能與 int **v 等同起來。 舉個例子&#xff1a; int **p; int (*v)[10]; int a[10][10]; 如果寫 v a 是可以的。 如果寫 p a 將會引起一個類型不匹配的編譯錯誤。 二級指針&#xff08;int **p&#xff09;需要自己指向一個一級指針&#x…

draw_circle_mod預生成交互式圓形

目錄draw_circle_mod&#xff08;算子&#xff09;描述參數draw_circle_mod&#xff08;算子&#xff09; draw_circle_mod - 圓的交互式繪圖。 draw_circle_mod&#xff08;:: WindowHandle&#xff0c;RowIn&#xff0c;ColumnIn&#xff0c;RadiusIn&#xff1a;Row&#…

川崎機器人c#通訊(轉)

由于本人在工業自動化行業做機器視覺的工作&#xff0c;所以除了圖像處理方面要掌握外&#xff0c;還需要與工業機器人進行通信。最近學習了計算機與川崎機器人的TCP/IP通信&#xff0c;于是在這里記錄一下。 除了直接與機器人通信外&#xff0c;有一種方式是通過PLC間接通信&a…

模板類 Template Classes 以及模板類編譯時的處理

&#xfeff;&#xfeff;我們可以建立template classes&#xff0c;使它們能夠神奇地操作任何類型的資料。下面這個例子是讓CThree 類別儲存三個成員變量&#xff0c;成員函數Min 傳回其中的最小值&#xff0c;成員函數Max 則傳回其中的最大值。我們把它設計為template class&…

行轉列及列轉行查詢

開發過程中常遇到行轉列或是列轉行的問題&#xff0c;即需要將數據庫中一張表信息進行行轉列操作&#xff0c;再將每列&#xff08;即每個字段&#xff09;作為與其他表進行聯表查詢的字段進行顯示。 一、行轉列&#xff1a;將原來同一列下多行的不同內容作為多個字段&#xff…

移動端系列講解之字體單位

移動端字體單位有哪些&#xff1f;他們的兼容性如何&#xff1f;他們的特點&#xff1f;1.移動端字體單位現在主要有 em 、rem 、px 2.兼容性請傳送點擊這里 em: em是相對長度單位。相對于父元素設置的字體大小。em相對于當前對象內文本的字體尺寸。如當前對行內文本的字體尺寸…

x264_param_t參數注解

typedef struct x264_param_t { CPU 標志位 unsigned int cpu; int i_threads; 并行編碼多幀 int b_deterministic; 是否允許非確定性時線程優化 int i_sync_lookahead; 線程超前緩沖 視頻屬性 int i_width; 寬度 int i_height; 高…

gen_circle_contour_xld創建圓或圓弧的XLD輪廓

目錄gen_circle_contour_xld&#xff08;算子&#xff09;描述參數gen_circle_contour_xld&#xff08;算子&#xff09; gen_circle_contour_xld - 創建圓或圓弧的XLD輪廓。 gen_circle_contour_xld&#xff08;&#xff1a;ContCircle&#xff1a;Row&#xff0c;Column&am…

一、Java語言基礎(4)_方法和數組——數組

2018-04-25 不悔夢歸處&#xff0c;只恨未盡心 數組 一、一維數組 數組的含義&#xff1a;具有相同類型的多個變量按有序形式組織起來的數據形式。&#xff08;數組是用來存儲固定大小的同類型元素。&#xff09;數組的定義&#xff1a;方式1&#xff08;推薦使用&#xff09;&…

http header 具體解釋

HTTP&#xff08;HyperTextTransferProtocol&#xff09;即超文本傳輸協議&#xff0c;眼下網頁傳輸的的通用協議。HTTP協議採用了請求/響應模型&#xff0c;瀏覽器或其它client發出請求&#xff0c;server給與響應。就整個網絡資源傳輸而言&#xff0c;包含message-header和me…

研究生開題報告需要注意的幾點

&#xfeff;&#xfeff;1 畢業論文選題的原則 畢業論文選題一般要求滿足以下原則&#xff1a; ①開拓性:前人沒有專門研究過或雖已研究但尚無理想的結果&#xff0c;有待進一步的探討和研究&#xff0c;或是學術界有分歧&#xff0c;有必要深入研究探討的問題&#xff1b;…

create_metrology_model創建測量幾何形狀所需的數據結構(原理)

目錄create_metrology_model&#xff08;算子&#xff09;描述二維計量的基本原理創建計量模型數據結構提供近似值修改模型參數修改對象參數對齊計量模型應用測量訪問結果清理記憶注意參數create_metrology_model&#xff08;算子&#xff09; create_metrology_model - 創建測…