设为首页 加入收藏

TOP

12.1 识别类和类之间的关系(12)
2013-10-07 14:30:53 来源: 作者: 【 】 浏览:60
Tags:12.1 识别 类和 之间 关系

12.1 识别类和类之间的关系(12)

显而易见,这是一个析构函数的代理,它的任务是负责调用析构函数,然后根据参数值调用delete。将这个函数重命名为_Destructor_4011E0,重命名后,虚表结构是这个样子:

  1. .rdata:0040C0D0 vTable_40C0D0 dd offset _Destructor_4011E0  
  2. .rdata:0040C0D4 pfnShowShtring dd offset ShowShtring  
  3. .rdata:0040C0D8 pfnGetCChinese dd offset GetCChinese  
  4. _Destructor_4011E0函数是虚表的第一项,我们可以回到main函数中来观察其参数传递的过程:  
  5. .text:00401103     test esi, esi  
  6. ; 当对象指针esi不为0时执行_Destructor_4011E0  
  7. .text:00401105     jz  short loc_40110F  
  8. .text:00401107     mov edx, [esi] ; edx获得虚表  
  9. .text:00401109     push 1   ; 传递参数值1  
  10. .text:0040110B     mov ecx, esi ; 传递this指针  
  11. .text:0040110D     call dword ptr [edx] ; 调用_Destructor_4011E0  
  12. .text:0040110F  
  13. .text:0040110F loc_40110F: 

在main函数中调用虚表第一项时传递的值为1,那么在_Destructor_4011E0函数中,执行完析构函数后就会调用delete释放对象的内存空间。为什么要用这样一个参数来控制函数内释放空间的行为呢?为什么不能直接释放呢?

因为析构函数和释放堆空间是两回事,有的程序员喜欢自己维护析构函数,或者反复使用同一个堆对象,这时显式调用析构函数的同时不能释放堆空间,如下代码所示:

  1. void main(int argc, char* argv[]){  
  2.   CPerson *pPerson = new CChinese;  
  3.   pPerson->ShowSpeak();  
  4.   pPerson->~CPerson(); // 显式调用析构函数  
  5.  
  6. // 将堆内存中pPerson指向的地址作为CChinese的新对象的首地址,并调用CChinese的构造函数。这  
  7. // 样可以重复使用同一个堆内存,以节约内存空间  
  8.   pPerson = new(pPerson)CChinese();  
  9.   delete pPerson;  

由于显式调用析构函数时不能马上释放堆内存,因此在析构函数的代理函数中通过一个参数来控制是否释放内存,以便于程序员自己管理析构函数的调用。这个代理函数的反汇编代码很简单,请读者自己上机验证。

在通过分析反汇编代码来识别类关系时,对于含有虚函数的类而言,利用IDA的交叉参考功能可简化分析识别过程。根据以上分析可知,具有虚函数,必然存在虚表指针。为了初始化虚表指针必然要准备构造函数,有了构造函数就可利用以上方法,顺藤摸瓜得到类关系,还原出对象模型。

思考题 大家在调试以上程序时会发现,比如CChinese的对象,在构造函数执行时虚表已经初始化完成了,在析构函数执行时,其虚表指针已经是子类的虚表了,为什么编译器还要在析构函数中再次将虚表设置为子类虚表呢?这是冗余操作吗?如果不这么做,会引发什么后果?答案见本章小结。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇12.1 识别类和类之间的关系(2) 下一篇12.4 菱形继承(4)

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: