5.7 do/while/for的比较(2)
如果遇到以上代码块,即可判定它为一个do循环结构,只有do循环结构无需先检查,直接执行循环语句块。根据条件跳转指令所跳转到的地址,可以得到循环语句块的首地址,jxx指令的地址为循环语句块的结尾地址。在还原while比较时,应该注意,它与if不同,while的比较数并不是相反,而是相同的。依此分析即可还原do循环结构的原型。
(2)while循环
while循环和do循环正好相反,在执行循环语句块之前,必须要进行条件判断,根据比较结果再选择是否执行循环语句块,如代码清单5-23所示。
代码清单5-23 while循环—Debug版
- // C++(www.cppentry.com)源码说明:while循环完成整数累加和
- int LoopWhile(int nCount){
- int nSum = 0;
- int nIndex = 0;
- // 先执行条件比较,再进入循环体
- while (nIndex <= nCount){
- nSum += nIndex;
- nIndex++;
- }
- return nSum;
- }
-
- // C++(www.cppentry.com)源码于对应汇编代码讲解
- int nSum = 0;
- 0040B7C8 mov dword ptr [ebp-4],0
- int nIndex = 0;
- 0040B7CF mov dword ptr [ebp-8],0
- // C++(www.cppentry.com)源码对比,判断循环条件
- while (nIndex <= nCount)
- 0040B7D6 mov eax,dword ptr [ebp-8]
- 0040B7D9 cmp eax,dword ptr [ebp+8]
- ; 条件判断比较,使用JG指令,大于则跳转到地址0x0040B7F2处,和if语句一样
- ; 地址0x0040B7F2为while循环结束地址
- 0040B7DC jg LoopWhile+42h (0040b7f2)
- {
- // 循环语句块
- nSum += nIndex;
- 0040B7DE mov ecx,dword ptr [ebp-4]
- 0040B7E1 add ecx,dword ptr [ebp-8]
- 0040B7E4 mov dword ptr [ebp-4],ecx
- nIndex++;
- 0040B7E7 mov edx,dword ptr [ebp-8]
- 0040B7EA add edx,1
- 0040B7ED mov dword ptr [ebp-8],edx
- }
- ; 执行跳转指令JMP,跳转到地址0x0040B7D6处
- 0040B7F0 jmp LoopWhile+26h (0040b7d6)
- return nSum;
- 0040B7F2 mov eax,dword ptr [ebp-4]
在代码清单5-23中,转换后的while比较和if语句一样,也是比较相反,向下跳转。如何区分代码中是分支结果还是循环结构呢?查看条件指令跳转地址0x0040B7F2,如果这个地址上有一句JMP指令,并且此指令跳转到的地址小于当前代码地址,那么很明显是一个向上跳转。要完成语句循环,就需要修改程序流程,回到循环语句处,因此向上跳转就成了循环结构的明显特征。根据这些特性可知while循环结构的特征,在条件跳转到的地址附近会有JMP指令修改程序流程,向上跳转,回到条件比较指令处。
while循环结构中使用了两次跳转指令完成循环,由于多使用了一次跳转指令,因此while循环要比do循环效率低一些。
总结:
- WHILE_BEGIN:
- ; 影响标记位的指令
- jxx WHILE_END ; 条件成立跳转到循环语句块结尾处
- …… ; 循环语句块
- jmp WHILE_BEGIN ; 跳转到取出条件比较数据处
- WHILE_END:
遇到以上代码块,即可判定它为一个while循环结构。根据条件跳转指令,可以还原相反的while循环判断。循环语句块的结尾地址即为条件跳转指令的目标地址,在这个地址之前会有一条jmp跳转指令,指令的目标地址为while循环的起始地址。需要注意的是,while循环结构很可能会被优化成do循环结构,被转换后的while结构由于需要检查是否可以被成功执行一次,通常会被嵌套在if单分支结构中,其还原的高级代码如下所示: - if(xxx)
- {
- do
- {
- // ……
- }while(xxx)
- }
(3)for循环
for循环是三种循环结构中最复杂的一种。for循环由赋初值、设置循环条件、设置循环步长这三条语句组成。由于for循环更符合人类的思维方式,在循环结构中被使用的频率也最高。根据for语句组成特性分析代码清单5-24。
代码清单5-24 for循环结构—Debug版
- // C++(www.cppentry.com)源码说明:for循环完成整数累加和
- int LoopFor(int nCount){
- int nSum = 0;
- // 初始计数器变量、设置循环条件、设置循环步长
- for (int nIndex = 0; nIndex <= nCount; nIndex++){
- nSum += nIndex;
- }
- return nSum;
- }
-
- // C++(www.cppentry.com)源码于对应汇编代码讲解
- int nSum = 0;
- 0040B818 mov dword ptr [ebp-4],0
- // C++(www.cppentry.com)源码对比,for语句
- for (int nIndex = 0; nIndex <= nCount; nIndex++)
- ;=====================================================
- ; 初始化计数器变量—nIndex 1.赋初值部分
- 0040B81F mov dword ptr [ebp-8],0
- ; 跳转到地址0x0040B831处,跳过步长操作
- 0040B826 jmp LoopFor+31h (0040b831)
- ;=====================================================
- ; 取出计数器变量,用于循环步长 2.步长计算部分
- 0040B828 mov eax,dword ptr [ebp-8]
- ; 对计数器变量执行加1操作,步长值为1
- 0040B82B add eax,1
- ; 将加1后的步长值放回计数器变量—nIndex
- 0040B82E mov dword ptr [ebp-8],eax
- ;=====================================================
- ; 取出计数器变量nIndex放入ecx 3.条件比较部分
- 0040B831 mov ecx,dword ptr [ebp-8]
- ; ebp+8地址处存放数据为参数nCount,见C++(www.cppentry.com)源码说明
- 0040B834 cmp ecx,dword ptr [ebp+8]
- ; 比较nIndex与nCount,大于则跳转到地址0x0040B844处,结束循环
- 0040B837 jg LoopFor+44h (0040b844)
- ;=====================================================
- {
- // for循环内执行语句块
- nSum += nIndex;
- mov edx,dword ptr [ebp-4] ; 4.循环体代码
- 0040B83C add edx,dword ptr [ebp-8]
- 0040B83F mov dword ptr [ebp-4],edx
- }
- ; 跳转到地址0x0040B828处,这是一个向上跳
- 0040B842 jmp LoopFor+28h (0040b828)
- return nSum;
- // 设置返回值eax为ebp-4,即nSum
- 0040B844 mov eax,dword ptr [ebp-4]