最近為了徹底弄清楚C++的物件模型,編譯器又是如何?多態的,虛表到底是怎樣的,所以在這裡自己寫了點代碼來實現和推敲C++的物件模型.
首先定義如下繼承體系:原始碼
然後構造對象指標,代碼如下:
int _tmain(int argc, _TCHAR* argv[])
{
IVtbl* pVtbl=NULL;
//vt to real1
int iSize = sizeof(VtblReal1);
pVtbl = new VtblReal1();
void (__thiscall VtblReal1::* pfn)(void)=&VtblReal1::f2;//成員函數指標聲明賦值
//void* pTemp = (void*)(&VtblReal1::f2);
if(pVtbl != NULL)
{
pVtbl->f1();
pVtbl->f2();
((VtblReal1*)pVtbl->*pfn)();//成員函數指標調用
delete pVtbl;
}
//vt to realb
pVtbl = new VtblRealB();
if(pVtbl != NULL)
{
pVtbl->f1();
delete pVtbl;
}
//vt to diamond
//IVtbl* pVtbl = new VtblDiamond();//轉換不明確VtblReal1還是VtblRealB???糾結
VtblReal1* pVtblReal1 = new VtblDiamond();
if(pVtblReal1 != NULL)
{
pVtblReal1->f1();
VtblRealB* pTempVtbB = dynamic_cast<VtblRealB*>(pVtblReal1);
pTempVtbB->f1();
delete pVtblReal1;
}
VtblRealB* pVtbRealB = new VtblDiamond();
if(pVtbRealB != NULL)
{
IVtbl* pVtbl = dynamic_cast<IVtbl*>(pVtbRealB);
pVtbl->f1();
delete pVtbRealB;
}
return 0;
}
執行結果如下:
再讓我們反組譯碼調試看看編譯器是如何幫我們實現的?
讓我們從彙編角度來看一個對象的初始化
// VtblDemo.cpp : 定義控制台應用程式的進入點。
//
#include "stdafx.h"
#include "IVtbl.h"
#include "VtblBase.h"
#include "VtblReal1.h"
#include "VtblRealB.h"
#include "VtblDiamond.h"
int _tmain(int argc, _TCHAR* argv[])
{
00961690 push ebp
00961691 mov ebp,esp
00961693 push 0FFFFFFFFh
00961695 push offset __ehhandler$_wmain (965468h)
0096169A mov eax,dword ptr fs:[00000000h]
009616A0 push eax
009616A1 sub esp,1DCh //申請堆棧空間
009616A7 push ebx
009616A8 push esi
009616A9 push edi
009616AA lea edi,[ebp-1E8h]
009616B0 mov ecx,77h
009616B5 mov eax,0CCCCCCCCh
009616BA rep stos dword ptr es:[edi]
009616BC mov eax,dword ptr [___security_cookie (96A0A8h)]
009616C1 xor eax,ebp
009616C3 push eax
009616C4 lea eax,[ebp-0Ch]
009616C7 mov dword ptr fs:[00000000h],eax
IVtbl* pVtbl=NULL;
009616CD mov dword ptr [ebp-14h],0 //初始化pVtbl指標為NULL
//vt to real1
int iSize = sizeof(VtblReal1);
009616D4 mov dword ptr [ebp-20h],0Ch //對ebp-20h也就是Isize局部變數賦值sizeof(VtblReal1)
//?why=0ch=(sizeof(vftable)+sizeof(DWORD m_dwValue)+sizeof(m_BaseVal))==12
pVtbl = new VtblReal1();
009616DB push 0Ch //sizeof(VtblReal1)
009616DD call operator new (9611E0h) //內部調用malloc來申請堆空間
009616E2 add esp,4
009616E5 mov dword ptr [ebp-140h],eax //將申請的記憶體位址賦予ebp-140h(pVtbl指標)
009616EB mov dword ptr [ebp-4],0
009616F2 cmp dword ptr [ebp-140h],0 //比較是否為NULL,如果為空白則跳轉到96170Eh
009616F9 je wmain+7Eh (96170Eh)
009616FB mov ecx,dword ptr [ebp-140h] //將申請的記憶體位址賦予ecx
00961701 call VtblReal1::VtblReal1 (96112Ch) //調用VtblReal1的建構函式??建構函式做些什麼?請查閱如下的VtblReal1建構函式詳解
00961706 mov dword ptr [ebp-1E4h],eax //將傳回值賦予ebp-1e4h??ebp-140h查看如下的對象初始化研究
0096170C jmp wmain+88h (961718h)
0096170E mov dword ptr [ebp-1E4h],0
00961718 mov eax,dword ptr [ebp-1E4h]
0096171E mov dword ptr [ebp-14Ch],eax
00961724 mov dword ptr [ebp-4],0FFFFFFFFh
0096172B mov ecx,dword ptr [ebp-14Ch]
00961731 mov dword ptr [ebp-14h],ecx
void (__thiscall VtblReal1::* pfn)(void)=&VtblReal1::f2;//成員函數指標聲明賦值
00961734 mov dword ptr [ebp-2Ch],offset VtblReal1::`vcall'{4}' (9611FEh) //VtblReal1::`vcall'{4}':
//00DA11FE jmp VtblReal1::`vcall'{4}' (0DA1C80h)
//00DA1C80 mov eax,dword ptr [ecx]
//00DA1C82 jmp dword ptr [eax+4]
//跳轉到ecx+4 VtblReal1::f2()地址
//void* pTemp = (void*)(&VtblReal1::f2);
if(pVtbl != NULL)
0096173B cmp dword ptr [ebp-14h],0
0096173F je wmain+128h (9617B8h)
{
pVtbl->f1();
00961741 mov eax,dword ptr [ebp-14h]
00961744 mov edx,dword ptr [eax]
00961746 mov esi,esp
00961748 mov ecx,dword ptr [ebp-14h]
0096174B mov eax,dword ptr [edx] //虛表第一個函數地址對應f1函數
0096174D call eax //調用f1
0096174F cmp esi,esp
00961751 call @ILT+400(__RTC_CheckEsp) (961195h)
pVtbl->f2();
00961756 mov eax,dword ptr [ebp-14h]
00961759 mov edx,dword ptr [eax]
0096175B mov esi,esp
0096175D mov ecx,dword ptr [ebp-14h]
00961760 mov eax,dword ptr [edx+4] //虛表地址+4第二個函數地址f2函數
00961763 call eax //調用f2
00961765 cmp esi,esp
00961767 call @ILT+400(__RTC_CheckEsp) (961195h)
((VtblReal1*)pVtbl->*pfn)();//成員函數指標調用
0096176C mov esi,esp
0096176E mov ecx,dword ptr [ebp-14h]
00961771 call dword ptr [ebp-2Ch] //this->f2(),ecx為this指標
00961774 cmp esi,esp
00961776 call @ILT+400(__RTC_CheckEsp) (961195h)
delete pVtbl;
0096177B mov eax,dword ptr [ebp-14h]
0096177E mov dword ptr [ebp-128h],eax
00961784 mov ecx,dword ptr [ebp-128h]
0096178A mov dword ptr [ebp-134h],ecx
00961790 cmp dword ptr [ebp-134h],0
00961797 je wmain+11Eh (9617AEh)
00961799 push 1
0096179B mov ecx,dword ptr [ebp-134h]
009617A1 call IVtbl::`scalar deleting destructor' (961064h) //調用解構函式進行資源釋放
009617A6 mov dword ptr [ebp-1E4h],eax
009617AC jmp wmain+128h (9617B8h)
009617AE mov dword ptr [ebp-1E4h],0
}
…...
//vt to diamond
//IVtbl* pVtbl = new VtblDiamond();//轉換不明確VtblReal1還是VtblRealB???糾結
VtblReal1* pVtblReal1 = new VtblDiamond();
…...
VtblRealB* pVtbRealB = new VtblDiamond();
00961950 push 38h
00961952 call operator new (9611E0h)
00961957 add esp,4
0096195A mov dword ptr [ebp-170h],eax
00961960 mov dword ptr [ebp-4],3
00961967 cmp dword ptr [ebp-170h],0
0096196E je wmain+2F3h (961983h)
00961970 mov ecx,dword ptr [ebp-170h]
00961976 call VtblDiamond::VtblDiamond (961258h)
0096197B mov dword ptr [ebp-1E4h],eax
00961981 jmp wmain+2FDh (96198Dh)
00961983 mov dword ptr [ebp-1E4h],0
0096198D mov eax,dword ptr [ebp-1E4h]
00961993 mov dword ptr [ebp-17Ch],eax
00961999 mov dword ptr [ebp-4],0FFFFFFFFh
009619A0 cmp dword ptr [ebp-17Ch],0
009619A7 je wmain+32Ah (9619BAh)
009619A9 mov ecx,dword ptr [ebp-17Ch]
009619AF add ecx,0Ch
009619B2 mov dword ptr [ebp-1E8h],ecx
009619B8 jmp wmain+334h (9619C4h)
009619BA mov dword ptr [ebp-1E8h],0
009619C4 mov edx,dword ptr [ebp-1E8h]
009619CA mov dword ptr [ebp-50h],edx
if(pVtbRealB != NULL)
009619CD cmp dword ptr [ebp-50h],0
009619D1 je wmain+39Bh (961A2Bh)
{
IVtbl* pVtbl = dynamic_cast<IVtbl*>(pVtbRealB);//將VtblDiamond類型指標轉換為Ivtbl指標...
009619D3 mov eax,dword ptr [ebp-50h]
009619D6 mov dword ptr [pVtbl],eax
pVtbl->f1();
009619D9 mov eax,dword ptr [pVtbl]
009619DC mov edx,dword ptr [eax]
009619DE mov esi,esp
009619E0 mov ecx,dword ptr [pVtbl]
009619E3 mov eax,dword ptr [edx]
009619E5 call eax
009619E7 cmp esi,esp
009619E9 call @ILT+400(__RTC_CheckEsp) (961195h)
delete pVtbRealB;
009619EE mov eax,dword ptr [ebp-50h]
009619F1 mov dword ptr [ebp-158h],eax
009619F7 mov ecx,dword ptr [ebp-158h]
009619FD mov dword ptr [ebp-164h],ecx
00961A03 cmp dword ptr [ebp-164h],0
00961A0A je wmain+391h (961A21h)
00961A0C push 1
00961A0E mov ecx,dword ptr [ebp-164h]
00961A14 call VtblRealB::`scalar deleting destructor' (961091h) //請看如下的對象析構過程研究
//在這裡請大家實驗沒有聲明virtual的解構函式與聲明virtual的解構函式的區別
//在本人的環境中沒有使用Virtual聲明的解構函式會導致vc調試器提示宣告失敗
。。。
對象初始化研究
ebp 0x002ffb10 009616DB push 0Ch 009616DD call operator new (9611E0h) 009616E2 add esp,4 009616E5 mov dword ptr [ebp-140h],eax ebp-140h eax(operator new返回的指標) 00961701 call VtblReal1::VtblReal1 (96112Ch) 00961706 mov dword ptr [ebp-1E4h],eax ebp-1E4h eax(建構函式返回內容) EBP = 002FFB10 EBP-140H = 0x002FF9D0 EBP-1E4H = 0x002FF92C 看看兩個都儲存些什麼內容? 0x002FF9D0 004b4f58 cccccccc cccccccc cccccccc cccccccc 0x002FF92C 004b4f58 cccccccc cccccccc cccccccc cccccccc //指向相同地址?那麼裡面儲存的又是神馬內容呢? 0x004B4F58 00967818 00000000 00000002 fdfdfdfd abababab abababab 00000000 地址 值 值 無初始化資料區域....?繼續跟一下看看 0x00967818 009610d2 0096100a 00000000 009688a8 00961028 0096115e 00000000 009688c0 009610f0 00000000 地址 地址 好像是數組?? VtblReal1::f1: 009610D2 jmp VtblReal1::f1 (961DE0h) VtblReal1::f2: 0096100A jmp VtblReal1::f2 (961D70h) 嗯哼原來是VtblReal1的函數地址,那麼0x00967818就是指向VtblReal1對象的虛表地址咯 而後續的00000000和00000002分別代表VtblBase::m_BaseVal(0)和VtblReal1::m_dwValue(2) 所以可以得出結論 new 出來的對象和初始化返回的結構都是指向VtblBase對象地址 讓我們重新開始那麼這裡的new和建構函式又做了些什麼呢 new初始化地址記憶體為cdcdcdcd顯然未初始化狀態 調用建構函式之後則出現了 0x003F4F58 00da7818 00000000 00000002 對應虛表和成員變數,如此可以肯定建構函式完成了對象的初始化 |
對象析構過程研究
VtblRealB::`scalar deleting destructor': 00DA1091 jmp VtblRealB::`scalar deleting destructor' (0DA1C10h) VtblRealB::`scalar deleting destructor': 00DA1C10 push ebp //VC提供地址 00DA1C11 mov ebp,esp 00DA1C13 sub esp,0CCh 00DA1C19 push ebx 00DA1C1A push esi 00DA1C1B push edi 00DA1C1C push ecx 00DA1C1D lea edi,[ebp-0CCh] 00DA1C23 mov ecx,33h 00DA1C28 mov eax,0CCCCCCCCh 00DA1C2D rep stos dword ptr es:[edi] 00DA1C2F pop ecx 00DA1C30 mov dword ptr [ebp-8],ecx 00DA1C33 mov ecx,dword ptr [this] 00DA1C36 call VtblRealB::~VtblRealB (0DA1041h) //調用VtblRealB的解構函式 00DA1C3B mov eax,dword ptr [ebp+8] 00DA1C3E and eax,1 00DA1C41 je VtblRealB::`scalar deleting destructor'+3Fh (0DA1C4Fh) 00DA1C43 mov eax,dword ptr [this] //將this指標賦予eax寄存器 00DA1C46 push eax 00DA1C47 call operator delete (0DA10B4h) //調用delete函數進行刪除 00DA1C4C add esp,4 //棧平衡 00DA1C4F mov eax,dword ptr [this] 00DA1C52 pop edi 00DA1C53 pop esi 00DA1C54 pop ebx 00DA1C55 add esp,0CCh 00DA1C5B cmp ebp,esp 00DA1C5D call @ILT+400(__RTC_CheckEsp) (0DA1195h) 00DA1C62 mov esp,ebp 00DA1C64 pop ebp 00DA1C65 ret 4 |
如此基本上已經可以比較清晰的觀察出編譯器是如何協助我們來控制一個對象的生老病死了,即一個指標對象的完整生命週期.