设为首页 加入收藏

TOP

12.1 识别类和类之间的关系(6)
2013-10-07 14:31:07 来源: 作者: 【 】 浏览:69
Tags:12.1 识别 类和 之间 关系

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

我们来修改代码清单12-5中的构造函数和析构函数的实现过程,通过调试来分析其执行过程,如代码清单12-7所示。

代码清单12-7 构造函数和析构函数中调用虚函数的流程

  1. // 修改代码清单12-5后的示例,在构造函数与析构函数中添加虚函数调用  
  2. class CPerson{                  // 基类—"人"类  
  3. public:  
  4.     CPerson(){  
  5.         ShowSpeak();                // 添加虚函数调用  
  6.     }  
  7.     virtual ~CPerson(){  
  8.         ShowSpeak();                // 添加虚函数调用  
  9.     }  
  10.     virtual void ShowSpeak(){  
  11.         printf("Speak No\r\n");  
  12.     }  
  13. };  
  14. // main函数实现过程  
  15. void main(int argc, char* argv[]){  
  16.     CChinese Chinese;  
  17. }  
  18.  
  19. // C++(www.cppentry.com)源码与汇编代码对比分析  
  20. // Chinese 构造函数调用过程分析  
  21. CChinese(){}  
  22. 00401139    pop     ecx                 ; 还原this指针  
  23. 0040113A    mov     dword ptr [ebp-4],ecx  
  24. 0040113D    mov     ecx,dword ptr [ebp-4]   ; 传入当前this指针,将其作为父类的this指针  
  25. 00401140    call        @ILT+30(CPerson::CPerson) (00401023)    ; 调用父类构造函数  
  26. ; 执行父类构造函数后,将虚表设置为子类的虚表  
  27. 00401145    mov     eax,dword ptr [ebp-4]   ; 获取this指针,这个指针也是虚表指针  
  28. 00401148    mov     dword ptr [eax],offset CChinese::'vftable' (0042201c)  
  29. ; 设置虚表指针为子类的虚表  
  30. 0040114E    mov     eax,dword ptr [ebp-4]   ; 将返回值设置为this指针  
  31. // 父类构造函数分析  
  32. CPerson(){}  
  33. 00401199    pop     ecx     ; 还原this指针,此时指针为子类对象的首地址  
  34. 0040119A    mov     dword ptr [ebp-4],ecx  
  35. 0040119D    mov     eax,dword ptr [ebp-4]   ; 取出子类的虚表指针,设置为父类虚表  
  36. 004011A0    mov     dword ptr [eax],offset CPerson::'vftable' (00422028)  
  37.     ShowSpeak();  
  38. 004011A6    mov     ecx,dword ptr [ebp-4]   ; 虚表是父类的,可以直接调用父类虚函数  
  39. 004011A9    call    @ILT+15(CPerson::ShowSpeak) (00401014)  
  40. 004011C1    ret  
  41.  
  42. // Chinese 析构函数调用过程分析  
  43. virtual ~CChinese(){}  
  44. 00401309    pop     ecx                 ; 还原this指针  
  45. 0040130A    mov     dword ptr [ebp-4],ecx  
  46. 0040130D    mov     eax,dword ptr [ebp-4]   ; 再次设置子类的虚表  
  47. 00401310    mov     dword ptr [eax],offset CChinese::'vftable' (0042201c)  
  48. 00401316    mov     ecx,dword ptr [ebp-4]   ; 调用父类的析构函数  
  49. 00401319    call    @ILT+20(CPerson::~CPerson) (00401019)  
  50. // 父类析构函数分析  
  51. virtual ~CPerson(){  
  52. 004012B9    pop     ecx  
  53. 004012BA    mov     dword ptr [ebp-4],ecx  
  54. 004012BD    mov     eax,dword ptr [ebp-4]  
  55. ; 由于当前虚表指针指向了子类虚表,需要重新修改为父类虚表,以防止调用子类的虚函数  
  56. 004012C0    mov     dword ptr [eax],offset CPerson::'vftable' (00422028)  
  57.     ShowSpeak();  
  58. 004012C6    mov     ecx,dword ptr [ebp-4]   ; 虚表是父类的,可以直接调用父类虚函数  
  59. 004012C9    call    @ILT+15(CPerson::ShowSpeak) (00401014)  
  60. }  
  61. 004012DE    ret 

在代码清单12-7的子类构造函数代码中,首先调用了父类的构造函数,然后设置虚表指针为当前类的虚表首地址。而析构函数中的顺序却与构造函数相反,首先设置虚表指针为当前类的虚表首地址,然后再调用父类的析构函数。其构造和析构的过程描述如下:

通过上面的分析可知构造和析构的顺序如下:

构造:基类→基类的派生类→……→当前类

析构:当前类→基类的派生类→ ……→基类

在代码清单12-5中,析构函数被定义为虚函数。为什么要将析构函数定义为虚函数呢?由于可以使用父类指针保存子类对象的首地址,因此当使用父类指针指向子类堆对象时,就会出问题。当使用delete释放对象的空间时,如果析构函数没有被定义为虚函数,那么编译器将会按指针的类型调用父类的析构函数,从而引发错误。而使用了虚析构函数后,会访问虚表并调用对象的析构函数。两种析构函数的调用过程如以下代码所示。

  1. // 没有声明为虚析构函数  
  2. CPerson * pPerson = new CChinese;  
  3. delete pPerson;                 // 部分代码分析略  
  4. mov     ecx,dword ptr [ebp-1Ch]     ; 直接调用父类的析构函数  
  5. call    @ILT+10(CPerson::'scalar deleting destructor') (0040100f)  
  6.  
  7. // 声明为虚析构函数  
  8. CPerson * pPerson = new CChinese;  
  9. delete pPerson;                 // 部分代码分析略  
  10. mov     ecx,dword ptr [ebp-1Ch]     ; 获取pPerson并保存到ecx中  
  11. mov     edx,dword ptr [ecx]         ; 取得虚表指针  
  12. mov     ecx,dword ptr [ebp-1Ch]     ; 传递this指针  
  13. call    dword ptr [edx]         ; 间接调用虚析构函数 

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

评论

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