用汇编的眼光看C++(之类继承) ,(之虚函数) .

用汇编的眼光看C++(之类继承)

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】


 

    继承是类的一个基本属性,可是在类的继承过程中,函数是怎么初始化?怎么析构的呢?我们不妨看看下面这样的一段代码?

 

  1. class employee  
  2. {  
  3. public:  
  4.     employee() { printf("employee()!\n");}  
  5.     ~employee() { printf("~employee()!\n");}  
  6. };  
  7.   
  8. class manager : public employee  
  9. {  
  10. public:  
  11.     manager() { printf("manager()!\n");}  
  12.     ~manager() {  printf("~maneger()!\n");}  
  13. };  

class employee
{
public:
	employee() { printf("employee()!\n");}
	~employee() { printf("~employee()!\n");}
};

class manager : public employee
{
public:
	manager() { printf("manager()!\n");}
	~manager() {  printf("~maneger()!\n");}
};

    看到上面的代码,相信大家也明白了,我们定义了这样一个类。基类是empoyee,继承类是manager。我们看到manager是一种特殊的employee,那么在内存构建和析构的时候函数又是怎么安排的呢?

 

 

  1. 74:       manager m;  
  2. 00401268   lea         ecx,[ebp-4]  
  3. 0040126B   call        @ILT+60(manager::manager) (00401041)  
  4. 75:   }  
  5. 00401270   lea         ecx,[ebp-4]  
  6. 00401273   call        @ILT+0(manager::~manager) (00401005)  
  7. 00401278   pop         edi  
  8. 00401279   pop         esi  
  9. 0040127A   pop         ebx  
  10. 0040127B   add         esp,44h  
  11. 0040127E   cmp         ebp,esp  
  12. 00401280   call        __chkesp (00408760)  
  13. 00401285   mov         esp,ebp  
  14. 00401287   pop         ebp  

74:       manager m;
00401268   lea         ecx,[ebp-4]
0040126B   call        @ILT+60(manager::manager) (00401041)
75:   }
00401270   lea         ecx,[ebp-4]
00401273   call        @ILT+0(manager::~manager) (00401005)
00401278   pop         edi
00401279   pop         esi
0040127A   pop         ebx
0040127B   add         esp,44h
0040127E   cmp         ebp,esp
00401280   call        __chkesp (00408760)
00401285   mov         esp,ebp
00401287   pop         ebp

    我们发现manager的构造和析构其实也简单。构造函数其实就是在变量出现的时候进行构造。那什么时候析构呢?也就在函数快结束的时候进行析构。下面我们可以进一步讨论在manager的构造和析构究竟是怎么做的?

 

 

  1. 65:   class manager : public employee  
  2. 66:   {  
  3. 67:   public:  
  4. 68:       manager() { printf("manager()!\n");}  
  5. 004012A0   push        ebp  
  6. 004012A1   mov         ebp,esp  
  7. 004012A3   sub         esp,44h  
  8. 004012A6   push        ebx  
  9. 004012A7   push        esi  
  10. 004012A8   push        edi  
  11. 004012A9   push        ecx  
  12. 004012AA   lea         edi,[ebp-44h]  
  13. 004012AD   mov         ecx,11h  
  14. 004012B2   mov         eax,0CCCCCCCCh  
  15. 004012B7   rep stos    dword ptr [edi]  
  16. 004012B9   pop         ecx  
  17. 004012BA   mov         dword ptr [ebp-4],ecx  
  18. 004012BD   mov         ecx,dword ptr [ebp-4]  
  19. 004012C0   call        @ILT+40(employee::employee) (0040102d)  
  20. 004012C5   push        offset string "manager()!\n" (00431020)  
  21. 004012CA   call        printf (004086e0)  
  22. 004012CF   add         esp,4  
  23. 004012D2   mov         eax,dword ptr [ebp-4]  
  24. 004012D5   pop         edi  
  25. 004012D6   pop         esi  
  26. 004012D7   pop         ebx  
  27. 004012D8   add         esp,44h  
  28. 004012DB   cmp         ebp,esp  
  29. 004012DD   call        __chkesp (00408760)  
  30. 004012E2   mov         esp,ebp  
  31. 004012E4   pop         ebp  
  32. 004012E5   ret  

65:   class manager : public employee
66:   {
67:   public:
68:       manager() { printf("manager()!\n");}
004012A0   push        ebp
004012A1   mov         ebp,esp
004012A3   sub         esp,44h
004012A6   push        ebx
004012A7   push        esi
004012A8   push        edi
004012A9   push        ecx
004012AA   lea         edi,[ebp-44h]
004012AD   mov         ecx,11h
004012B2   mov         eax,0CCCCCCCCh
004012B7   rep stos    dword ptr [edi]
004012B9   pop         ecx
004012BA   mov         dword ptr [ebp-4],ecx
004012BD   mov         ecx,dword ptr [ebp-4]
004012C0   call        @ILT+40(employee::employee) (0040102d)
004012C5   push        offset string "manager()!\n" (00431020)
004012CA   call        printf (004086e0)
004012CF   add         esp,4
004012D2   mov         eax,dword ptr [ebp-4]
004012D5   pop         edi
004012D6   pop         esi
004012D7   pop         ebx
004012D8   add         esp,44h
004012DB   cmp         ebp,esp
004012DD   call        __chkesp (00408760)
004012E2   mov         esp,ebp
004012E4   pop         ebp
004012E5   ret

    我们发现,manager的构造里面添加了employee的缺省构造函数,那么析构函数呢?

 

 

  1. 69:       ~manager() {  printf("~maneger()!\n");}  
  2. 00401350   push        ebp  
  3. 00401351   mov         ebp,esp  
  4. 00401353   sub         esp,44h  
  5. 00401356   push        ebx  
  6. 00401357   push        esi  
  7. 00401358   push        edi  
  8. 00401359   push        ecx  
  9. 0040135A   lea         edi,[ebp-44h]  
  10. 0040135D   mov         ecx,11h  
  11. 00401362   mov         eax,0CCCCCCCCh  
  12. 00401367   rep stos    dword ptr [edi]  
  13. 00401369   pop         ecx  
  14. 0040136A   mov         dword ptr [ebp-4],ecx  
  15. 0040136D   push        offset string "~maneger()!\n" (00431040)  
  16. 00401372   call        printf (004086e0)  
  17. 00401377   add         esp,4  
  18. 0040137A   mov         ecx,dword ptr [ebp-4]  
  19. 0040137D   call        @ILT+5(employee::~employee) (0040100a)  
  20. 00401382   pop         edi  
  21. 00401383   pop         esi  
  22. 00401384   pop         ebx  
  23. 00401385   add         esp,44h  
  24. 00401388   cmp         ebp,esp  
  25. 0040138A   call        __chkesp (00408760)  
  26. 0040138F   mov         esp,ebp  
  27. 00401391   pop         ebp  
  28. 00401392   ret  

69:       ~manager() {  printf("~maneger()!\n");}
00401350   push        ebp
00401351   mov         ebp,esp
00401353   sub         esp,44h
00401356   push        ebx
00401357   push        esi
00401358   push        edi
00401359   push        ecx
0040135A   lea         edi,[ebp-44h]
0040135D   mov         ecx,11h
00401362   mov         eax,0CCCCCCCCh
00401367   rep stos    dword ptr [edi]
00401369   pop         ecx
0040136A   mov         dword ptr [ebp-4],ecx
0040136D   push        offset string "~maneger()!\n" (00431040)
00401372   call        printf (004086e0)
00401377   add         esp,4
0040137A   mov         ecx,dword ptr [ebp-4]
0040137D   call        @ILT+5(employee::~employee) (0040100a)
00401382   pop         edi
00401383   pop         esi
00401384   pop         ebx
00401385   add         esp,44h
00401388   cmp         ebp,esp
0040138A   call        __chkesp (00408760)
0040138F   mov         esp,ebp
00401391   pop         ebp
00401392   ret

    我们发现,manager构造的时候employee率先构造,然后打印manager;析构的时候,恰恰相反,manager首先析构自己,然后在再调用employee的析构函数,上面的汇编代码证明了一切。

 

用汇编的眼光看C++(之虚函数)

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】  


    虚函数是面向对象设计中的一个重要内容。它的出现使得我们只需要相同的接口函数,并可以得到不同的生成结果。但是有些朋友却知其然,不知其所以然,为什么会出现这样的结果,我们可以用一段代码说明问题。首先,我们先定义两个基本类型,一个是employee,一个是manager,看过前面一片博客的朋友应该都有点印象:

  1. class employee  
  2. {  
  3. public:  
  4.     employee() { }  
  5.     ~employee() {}  
  6.     virtual void print() const { printf("employee!\n");}  
  7. };  
  8.   
  9. class manager : public employee  
  10. {  
  11. public:  
  12.     manager() {}  
  13.     ~manager() {}  
  14.     void print() const {printf("manager!\n");}  
  15. };  
class employee
{
public:
	employee() { }
	~employee() {}
	virtual void print() const { printf("employee!\n");}
};

class manager : public employee
{
public:
	manager() {}
	~manager() {}
	void print() const {printf("manager!\n");}
};

    我们看到,和前面出现的成员函数稍微有一些不同,这里的print函数之前出现了virtual。然而正是这个virtual发挥了巨大的作用。可以毫不夸张地说,没有虚函数,基本上就没有设计模式,也就无法体现C++语言在面向对象设计中的巨大优越性。下面我们看看这个virtual是怎样发挥作用的?

  1. 76:       employee p;  
  2. 0040128D   lea         ecx,[ebp-10h]  
  3. 00401290   call        @ILT+45(employee::employee) (00401032)  
  4. 00401295   mov         dword ptr [ebp-4],0  
  5. 77:       manager m;  
  6. 0040129C   lea         ecx,[ebp-14h]  
  7. 0040129F   call        @ILT+65(manager::manager) (00401046)  
  8. 004012A4   mov         byte ptr [ebp-4],1  
  9. 78:       employee* e = &p;  
  10. 004012A8   lea         eax,[ebp-10h]  
  11. 004012AB   mov         dword ptr [ebp-18h],eax  
  12. 79:       e->print();  
  13. 004012AE   mov         ecx,dword ptr [ebp-18h]  
  14. 004012B1   mov         edx,dword ptr [ecx]  
  15. 004012B3   mov         esi,esp  
  16. 004012B5   mov         ecx,dword ptr [ebp-18h]  
  17. 004012B8   call        dword ptr [edx]  
  18. 004012BA   cmp         esi,esp  
  19. 004012BC   call        __chkesp (00408870)  
  20. 80:       e = &m;  
  21. 004012C1   lea         eax,[ebp-14h]  
  22. 004012C4   mov         dword ptr [ebp-18h],eax  
  23. 81:       e->print();  
  24. 004012C7   mov         ecx,dword ptr [ebp-18h]  
  25. 004012CA   mov         edx,dword ptr [ecx]  
  26. 004012CC   mov         esi,esp  
  27. 004012CE   mov         ecx,dword ptr [ebp-18h]  
  28. 004012D1   call        dword ptr [edx]  
  29. 004012D3   cmp         esi,esp  
  30. 004012D5   call        __chkesp (00408870)  
  31. 82:   }  
76:       employee p;
0040128D   lea         ecx,[ebp-10h]
00401290   call        @ILT+45(employee::employee) (00401032)
00401295   mov         dword ptr [ebp-4],0
77:       manager m;
0040129C   lea         ecx,[ebp-14h]
0040129F   call        @ILT+65(manager::manager) (00401046)
004012A4   mov         byte ptr [ebp-4],1
78:       employee* e = &p;
004012A8   lea         eax,[ebp-10h]
004012AB   mov         dword ptr [ebp-18h],eax
79:       e->print();
004012AE   mov         ecx,dword ptr [ebp-18h]
004012B1   mov         edx,dword ptr [ecx]
004012B3   mov         esi,esp
004012B5   mov         ecx,dword ptr [ebp-18h]
004012B8   call        dword ptr [edx]
004012BA   cmp         esi,esp
004012BC   call        __chkesp (00408870)
80:       e = &m;
004012C1   lea         eax,[ebp-14h]
004012C4   mov         dword ptr [ebp-18h],eax
81:       e->print();
004012C7   mov         ecx,dword ptr [ebp-18h]
004012CA   mov         edx,dword ptr [ecx]
004012CC   mov         esi,esp
004012CE   mov         ecx,dword ptr [ebp-18h]
004012D1   call        dword ptr [edx]
004012D3   cmp         esi,esp
004012D5   call        __chkesp (00408870)
82:   }

    上面是一段函数调用的代码,代码可以稍微有点长。不过没有关系,我们可以按照代码的行数一行一行地去进行说明和理解。

    76行: 我们创建了employee类型的一个变量p,这个可以从后面的employee的构造函数可以看出来

    77行: 我们创建了manager类型的一个变量,这个也可以从后面的manager的构造函数看出

    78行: 我们创建一个指针临时变量e,它保存了变量p的地址,这一句也比较简单

    79行: 我们发现79句下面共有7句汇编,其中第三句、第六句、第七句是平衡堆栈的时候用的,和我们的调用没有关系。那么call的edx是什么东西呢?原来函数调用的顺序是这样的:edx -> [ecx]  ->[ebp-0x18],不知道大家看明白了没有。在内存的第一个字节记录一个指向print函数指针的指针,也就是edx。通过这个edx,我们就可以查找到位于edx地址的内容是什么。后来我们提取出来后发现[edx]的内容正是我们要查找的print函数地址。这里相当于一个二次寻址的过程。

    80行: 我们重新对临时变量e进行了赋值,此时e保存的是变量m的地址

    81行: 我们发现此时的寻找过程和79行惊奇地一致,原因就在于edx的内容不同罢了。也就是指向函数指针的指针发生了变化而已。


    试想一下,如果没有这个virtual函数,以上这段代码会发生什么差别呢?

  1. 76:       employee p;  
  2. 0040127D   lea         ecx,[ebp-10h]  
  3. 00401280   call        @ILT+45(employee::employee) (00401032)  
  4. 00401285   mov         dword ptr [ebp-4],0  
  5. 77:       manager m;  
  6. 0040128C   lea         ecx,[ebp-14h]  
  7. 0040128F   call        @ILT+65(manager::manager) (00401046)  
  8. 00401294   mov         byte ptr [ebp-4],1  
  9. 78:       employee* e = &p;  
  10. 00401298   lea         eax,[ebp-10h]  
  11. 0040129B   mov         dword ptr [ebp-18h],eax  
  12. 79:       e->print();  
  13. 0040129E   mov         ecx,dword ptr [ebp-18h]  
  14. 004012A1   call        @ILT+5(employee::print) (0040100a)  
  15. 80:       e = &m;  
  16. 004012A6   lea         ecx,[ebp-14h]  
  17. 004012A9   mov         dword ptr [ebp-18h],ecx  
  18. 81:       e->print();  
  19. 004012AC   mov         ecx,dword ptr [ebp-18h]  
  20. 004012AF   call        @ILT+5(employee::print) (0040100a)  
  21. 82:   }  
76:       employee p;
0040127D   lea         ecx,[ebp-10h]
00401280   call        @ILT+45(employee::employee) (00401032)
00401285   mov         dword ptr [ebp-4],0
77:       manager m;
0040128C   lea         ecx,[ebp-14h]
0040128F   call        @ILT+65(manager::manager) (00401046)
00401294   mov         byte ptr [ebp-4],1
78:       employee* e = &p;
00401298   lea         eax,[ebp-10h]
0040129B   mov         dword ptr [ebp-18h],eax
79:       e->print();
0040129E   mov         ecx,dword ptr [ebp-18h]
004012A1   call        @ILT+5(employee::print) (0040100a)
80:       e = &m;
004012A6   lea         ecx,[ebp-14h]
004012A9   mov         dword ptr [ebp-18h],ecx
81:       e->print();
004012AC   mov         ecx,dword ptr [ebp-18h]
004012AF   call        @ILT+5(employee::print) (0040100a)
82:   }

    很遗憾,这里就没有了动态查找的过程,所有的打印函数最终都指向了函数employee::print,此时多态性也不复存在了。


【预告: 下一片博客将介绍类中的静态变量和静态函数】

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值