用汇编的眼光看C++(之判断流程)

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

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


    在我们平常的编程当中,用于判断的地方很多,但主要有下面三种方式:if-else;switch;?:。其中最后一种方式在本质上和if-else是一样的。switch和if-else其实也一样,如果我们把switch改成if(...) {} else if(...) {} else {},那么你实现的效果和switch实际差不多,熟悉的朋友都会有这样的体验。或许有的朋友还是不太相信,大家可以自己用写实例比较看一下。

    (1) switch中的break重要吗?


 
 
  1. 21: int m = 10;
  2. 004017A8 mov dword ptr [ebp -4], 0Ah
  3. 22: switch(m)
  4. 23: {
  5. 004017AF mov eax,dword ptr [ebp -4]
  6. 004017B2 mov dword ptr [ebp -8],eax
  7. 004017B5 cmp dword ptr [ebp -8], 0Ah
  8. 004017B9 je process+ 33h ( 004017c3)
  9. 004017BB cmp dword ptr [ebp -8], 0Bh
  10. 004017BF je process+ 42h ( 004017d2)
  11. 004017C1 jmp process+ 4Fh ( 004017df)
  12. 24: case 10:
  13. 25: printf( "ten!\n");
  14. 004017C3 push offset string "ten!\n" ( 0046f028)
  15. 004017C8 call printf ( 004214d0)
  16. 004017CD add esp, 4
  17. 26: break;
  18. 004017D0 jmp process+ 4Fh ( 004017df)
  19. 27:
  20. 28: case 11:
  21. 29: printf( "eleven!\n");
  22. 004017D2 push offset string "eleven!\n" ( 0046f01c)
  23. 004017D7 call printf ( 004214d0)
  24. 004017DC add esp, 4
  25. 30: break;
  26. 31:
  27. 32: default:
  28. 33: break;
  29. 34: }
  30. 35: return;
  31. 36: }
    上面的汇编代码说明了有break的时候,函数是怎么编译的。我们看到从地址0x4017AF处, CPU开始集中对m进行判断。首先,m的数据被赋值到eax,然后eax拷贝到堆栈【ebp-8】的内存当中。接着开始比较数据10,即16进制0A,如果两者相等,代码跳转到0x4017C3处理;但是如果不相等呢,那么指令会按照原来的排列顺序继续向下走,和11继续比较,如果比较成功,那么就会到地址0x4017d2处执行,如果都不相等,那么只好跳出switch模块,到地址0x4017df处执行了。前面我们说到,如果数据和10或者11比较成功的话,那么就会跳转到相应的case语句处继续执行,可是比较结束后,还会跳转到原来的位置吗?汇编代码告诉我们,他们不会。因为case比较结束后,也要到地址ox4017df处报到。

    如果这里case10后面没有break呢?情况会不会不一样呢?


 
 
  1. 004017A8 mov dword ptr [ebp -4], 0Ah
  2. 22: switch(m)
  3. 23: {
  4. 004017AF mov eax,dword ptr [ebp -4]
  5. 004017B2 mov dword ptr [ebp -8],eax
  6. 004017B5 cmp dword ptr [ebp -8], 0Ah
  7. 004017B9 je process+ 33h ( 004017c3)
  8. 004017BB cmp dword ptr [ebp -8], 0Bh
  9. 004017BF je process+ 40h ( 004017d0)
  10. 004017C1 jmp process+ 4Dh ( 004017dd)
  11. 24: case 10:
  12. 25: printf( "ten!\n");
  13. 004017C3 push offset string "ten!\n" ( 0046f028)
  14. 004017C8 call printf ( 004214d0)
  15. 004017CD add esp, 4
  16. 26:
  17. 27: case 11:
  18. 28: printf( "eleven!\n");
  19. 004017D0 push offset string "eleven!\n" ( 0046f01c)
  20. 004017D5 call printf ( 004214d0)
  21. 004017DA add esp, 4
  22. 29: break;
  23. 30:
  24. 31: default:
  25. 32: break;
  26. 33: }
  27. 34: return;
  28. 35: }
    我们在case10后面取消了break语句。和原来的情形稍有不同,我们发现case10在执行了printf函数之后,并没有跳出当前的switch模块,而是继续执行case11的语句。所以在输出结果的时候,你会发现ten和eleven都有打印。看了这样清晰的流程之后,相信你对break的重要性又有了新的认识。在汇编面前,一切都一目了然。

    (2)if中的&&和||是怎么运算的?

    同样,我们可以看下面一段示例。


 
 
  1. 21: int m = 10;
  2. 004017A8 mov dword ptr [ebp- 4],0Ah
  3. 22: int n = 0;
  4. 004017AF mov dword ptr [ebp- 8], 0
  5. 23:
  6. 24: if( m == 10 && n == 0)
  7. 004017B6 cmp dword ptr [ebp- 4],0Ah
  8. 004017BA jne process+ 3Fh ( 004017cf)
  9. 004017BC cmp dword ptr [ebp- 8], 0
  10. 004017C 0 jne process+ 3Fh ( 004017cf)
  11. 25: {
  12. 26: printf( "&&!\n");
  13. 004017C2 push offset string "&&!\n" ( 0046f02 0)
  14. 004017C7 call printf ( 004214e 0)
  15. 004017CC add esp, 4
  16. 27: }
  17. 28:
  18. 29: if( m == 10 || n == 0)
  19. 004017CF cmp dword ptr [ebp- 4],0Ah
  20. 004017D3 je process+ 4Bh ( 004017db)
  21. 004017D5 cmp dword ptr [ebp- 8], 0
  22. 004017D9 jne process+ 58h ( 004017e8)
  23. 30: {
  24. 31: printf( "||");
  25. 004017DB push offset string "||" ( 0046f01c)
  26. 004017E 0 call printf ( 004214e 0)
  27. 004017E5 add esp, 4
  28. 32: }
    &&在C语言中是与的意思,而||是或的意思。与就是说,两者均为真;而或的意思是两方中一方为真即可。这在对应的汇编的语句上面也体现得淋漓尽致。首先我们看与的情形。从地址0x4017B6处地指令,我们发现首先比较的是m数据,然后比较的是n数据,这可以从他们在堆栈中的偏移值可以看出来。如果m和10比较,那么下面才有n和0比较的机会,一旦比较失败,就会跳转到地址0x4017cf处执行,跳出当前的判断模块。n和0比较也一样,只有两者都比较成功,才有机会进入地址0x4017C2处执行,打印&&。和与对应的是或,我们发现地址0x4017cf处开始比较的也是数据m,其次才是数据n。和与不同,m数据和n数据只要由一方成功,就会跳转到地址0x4017db处执行。只有两者都为假,才会跳出当前的模块。所以说,两个jne构成了与的基础,一个je和一个jne构成了或的基石。大家可以自己试试看如果&&的选项和||的选项不断进行叠加的时候会出现怎样的情形?试试看。


总结:

    if-else这种二分判断结构其实在现在的编程中特别重要,也特别基础。有一个关于二分法最显著的代码就是在顺序数据中进行二分查找。如果有兴趣的话,自己可以动笔试试?在这里,我有几个建议:

     (1)确保函数输入的索引范围有序 (start < end)

    (2)在计算中间点的时候注意不要范围溢出 (middle = start + (end - start) >> 1)

    (3)考虑把你的函数修改成通用的二分法查找函数(可以考虑用const void*和函数指针)



【预告: 下面一片博文的内容是用汇编的眼光看C++之循环判断】




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值