• 用汇编的眼光看C++(之退出流程) 四


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


        无论是在判断还是在循环的过程中,通常在遇到合适的条件的时候就会退出相应的模块。跳出模块运行的方式很多,break,continue,return都可以。下面我们就可以分别对他们进行将介绍。

        (1)continue只能用于循环,而break循环、判断都可以应用。两者有什么区别呢?

    1. 21:       for(int m = 10; m < 100;  m ++)  
    2. 00401638   mov         dword ptr [ebp-4],0Ah  
    3. 0040163F   jmp         process+2Ah (0040164a)  
    4. 00401641   mov         eax,dword ptr [ebp-4]  
    5. 00401644   add         eax,1  
    6. 00401647   mov         dword ptr [ebp-4],eax  
    7. 0040164A   cmp         dword ptr [ebp-4],64h  
    8. 0040164E   jge         process+55h (00401675)  
    9. 22:       {  
    10. 23:           if(m / 20 == 3)  
    11. 00401650   mov         eax,dword ptr [ebp-4]  
    12. 00401653   cdq  
    13. 00401654   mov         ecx,14h  
    14. 00401659   idiv        eax,ecx  
    15. 0040165B   cmp         eax,3  
    16. 0040165E   jne         process+42h (00401662)  
    17. 24:           {  
    18. 25:               break;  
    19. 00401660   jmp         process+55h (00401675)  
    20. 26:           }  
    21. 27:  
    22. 28:           if(m / 10 == 0)  
    23. 00401662   mov         eax,dword ptr [ebp-4]  
    24. 00401665   cdq  
    25. 00401666   mov         ecx,0Ah  
    26. 0040166B   idiv        eax,ecx  
    27. 0040166D   test        eax,eax  
    28. 0040166F   jne         process+53h (00401673)  
    29. 29:           {  
    30. 30:               continue;  
    31. 00401671   jmp         process+21h (00401641)  
    32. 31:           }  
    33. 32:       }  
    34. 00401673   jmp         process+21h (00401641)  
        我们在循环模块里面可以看到了代码做了两次判断。一次判断是查看m除以20是否等于3,一次判断是查看m除以10是否可以整除。同样,前面的判断结果是break,后面的结果是continue,那么两者之间的区别是什么呢?我们可以好好看一下。在第一次条件判断的时候,我们发现如果m除以20不等于3,那么下面指令直接跳转到0x00401662,也就是第二条件的入口处。但是如果第一个条件判断成功了,那么程序就会跳转到地址0x00401675,也就是说整个循环模块已经结束了。而continue呢?我们发现不管条件判断是否为真,那么指令都会跳转到0x00401641处执行,也就是循环+1的地方。这说明continue结束的只是本次循环,整个循环的流程还没有结束,而break表示当前已经推出了整个循环模块。如果大家对书本上的概念还有些模糊的话,那么汇编指令在这里已经把细节之处解释得明明白白了。


        (2)goto一般用的情况不多,但是有一种情况例外?

    1. void  process()  
    2. {  
    3.     if(/* get resources one */ == 0)  
    4.     {  
    5.         return;  
    6.     }  
    7.   
    8.     if(/* get resource two  */ == 0)  
    9.     {  
    10.         /* free resource one */  
    11.         return;  
    12.     }  
    13.   
    14.     if(/* get resource three */ == 0)  
    15.     {  
    16.         /* free resource two */  
    17.         /* free resource one */  
    18.         return;  
    19.     }  
    20.   
    21.     if(/* get resource four */ == 0)  
    22.     {  
    23.         /* free resource three */  
    24.         /* free resource two */  
    25.         /* free resource one */  
    26.         return;  
    27.     }  
    28.   
    29.     /* ... */  
    30.   
    31. }  
        通常情况下,我们创建一个对象时,需要各种各样的资源,等到所有资源都分配齐了,我们的代码才能开始工作。但是事实上,也存在很多分配失败的可能性。如果遇到了分配失败,那么我们就要对原来的资源进行释放处理。随着资源的总数越来越多,这个代码块就会显得越来越臃肿。那么有没有什么好的办法解决这一难题呢?goto就是一种好的选择。
    1. void  process()  
    2. {  
    3.     if(/* get resources one */ == 0)  
    4.     {  
    5.         return;  
    6.     }  
    7.   
    8.     if(/* get resource two  */ == 0)  
    9.     {  
    10.           
    11.         goto fail_one;  
    12.     }  
    13.   
    14.     if(/* get resource three */ == 0)  
    15.     {  
    16.         goto fail_two;  
    17.     }  
    18.   
    19.     if(/* get resource four */ == 0)  
    20.     {  
    21.           
    22.         goto fail_three;  
    23.     }  
    24.   
    25.     /* ... */  
    26. fail_three:  
    27.     /* free resource three */  
    28.   
    29. fail_two:  
    30.     /* free resource two */  
    31.   
    32. fail_one:  
    33.     /* free resource one */  
    34.     return;  
    35. }  

        (3) return也是跳出代码段一个很好的方法。

        它不光可以在函数中使用,也可以灵活运用在循环语句、判断语句中。要是在递归语句中,更是少不了他的身影。比如说,我们现在查看一个字符串是否满足这样一个结构:

        str = ab | a str b;

        那么换成函数代码应该怎么编写呢?大家可以自己先试一试,下面是我的一个方法:

    1. BOOL process_multi_char(const char* str, int length)  
    2. {  
    3.     if(2 == length)  
    4.         return !strncmp(str, "ab", strlen("ab")) ? TRUE : FALSE;  
    5.   
    6.     if(str[0] == 'a' && str[length -1] == 'b')  
    7.         return process_multi_char(str+1, length-2);  
    8.     else  
    9.         return FALSE;  
    10. }  
    11.   
    12. BOOL process(const char str[], int length)  
    13. {  
    14.     if(NULL == str ||  length < 2 || (0 != length % 2))  
    15.         return FALSE;  
    16.   
    17.     return process_multi_char(&str[0], length);  
    18. }  

         这里return的好处就是充分把字符串进行了拆分。我们按照定义,先从基本的问题下手,大事化小,小事化了。当然这里的小事就是字符串这有两个字符的情形,也是最底层函数的出口。要不然函数就会一致不停地处理下去,这是没有意义的。计算过程中一旦发现了不合法的运算,那么整个迭代的过程就会结束,快速回溯,输出结果。所以说要想写好递归或者是用到堆栈结构的代码时,必须考虑代码的入口和出口的把握,而return就是最好的选择,什么时候return,什么时候迭代都要把握时机,否则很容易陷入到死循环当中去。


  • 相关阅读:
    【字符编码】Java编码格式探秘
    【集合框架】JDK1.8源码分析之Collections && Arrays(十)
    【面试】shuffle函数的实现
    关于Jquery中ajax方法data参数用法的总结
    关于BootStrap下图标的显示问题
    dajngo权限管理
    Django下TemplateDoesNotExist 异常的解决方法:
    ubuntu安装有道
    python manage.py shell之后的一些错误:
    fat32转ntfs ,Win7系统提示对于目标文件系统文件过大解决教程
  • 原文地址:https://www.cnblogs.com/sier/p/5676493.html
Copyright © 2020-2023  润新知