深入研究c++对象模型
最近为了彻底弄清楚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 |
如此基本上已经可以比较清晰的观察出编译器是如何帮助我们来控制一个对象的生老病死了,即一个指针对象的完整生命周期.