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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/feixiaoxing/article/details/6779279

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱: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. class manager : public employee
  9. {
  10. public:
  11. manager() {}
  12. ~manager() {}
  13. void print() const { printf( "manager!\n");}
  14. };
    我们看到,和前面出现的成员函数稍微有一些不同,这里的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,这个可以从后面的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. 0040128 0 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: }
    很遗憾,这里就没有了动态查找的过程,所有的打印函数最终都指向了函数employee::print,此时多态性也不复存在了。


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


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值