深入研究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 

如此基本上已经可以比较清晰的观察出编译器是如何帮助我们来控制一个对象的生老病死了,即一个指针对象的完整生命周期.

posted @ 2012-03-09 23:53  Yarkin  阅读(993)  评论(0编辑  收藏  举报