zoukankan      html  css  js  c++  java
  • 用汇编的眼光看C++(之判断流程) 四

    【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱: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. 004017C0   jne         process+3Fh (004017cf)  
    11. 25:       {  
    12. 26:           printf("&&!\n");  
    13. 004017C2   push        offset string "&&!\n" (0046f020)  
    14. 004017C7   call        printf (004214e0)  
    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. 004017E0   call        printf (004214e0)  
    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*和函数指针)


  • 相关阅读:
    vue hover如何触发事件?
    防止 window.open 被拦截
    input输入框change和blur事件区别
    神马?使用JS直接上传并预览粘贴板的图片?
    删除设备与驱动器中百度网盘图标
    枚举类字典代码 草稿
    中文转换成阿拉伯数字
    Java对象与类中的一个小练习
    正向代理和反向代理
    MySQL教程126-MySQL事务隔离级别
  • 原文地址:https://www.cnblogs.com/sier/p/5676495.html
Copyright © 2011-2022 走看看