登录  | 立即注册

游客您好!登录后享受更多精彩

查看: 88|回复: 2

X86C++反汇编07.循环

[复制链接]

90

主题

-6

回帖

112

积分

网站编辑

积分
112
发表于 2025-5-5 11:53:52 | 显示全部楼层 |阅读模式

循环在 release 和 debug 版有重大区别

循环的难点在于处理代码外提和代码内联

DEBUG版

do while

do while 是3种循环中 仅跳一次的,而且判断条件是正向的

定式:

DO_BEGIN :

..........

jxx DO_BEGIN

DO_END :

do while 的循环条件是正方向 条件还小于等于 ,那么就是 while 里面的的条件小于等于,因为他的语义和汇编一致,满足条件就转移

int main(int argc, char* argv[])
{
   int nSum = 0;
   int i = 1;
   do
   {
      nSum = nSum + i;  
      i++;
   } while (i<=100);
   printf("%d\r\n",nSum);
   return 0;
}

反汇编代码
        mov     [ebp+var_4], 0
    mov     [ebp+var_8], 1
loc_40F986:                          
    mov     eax, [ebp+var_4]
    add     eax, [ebp+var_8]
    mov     [ebp+var_4], eax
    mov     ecx, [ebp+var_8]
    add     ecx, 1                                      ; i+1
    mov     [ebp+var_8], ecx
    cmp     [ebp+var_8], 64h ; 
    jle     short loc_40F986               ; 跳转往上跳
    mov     edx, [ebp+var_4]
    push    edx
    push    offset Format   ; "%d\r\n"
    call    _printf

分析: 往上跳可以直接还原成 do   while

while

定式:

WHILE_BEGIN :

jxx WHILE_END

.....

jmp WHILE_BEGIN ;往上跳就是while循环,往下跳就是 if else

WHILE_END:

jxx 的条件 跟 while 相反 jxx是满足条件条件跳走,while 是满足条件执行,2者相反

int main(int argc, char* argv[])
{
   int nSum = 0;
   int i = 1;
   while (i<=100)
   {
      nSum = nSum + i;  
      i++;
   } 
   printf("%d\r\n",nSum);
   return 0;
}
反汇编代码
         mov     [ebp+var_4], 0
     mov     [ebp+var_8], 1
loc_401036:            ; WHILE_BEGIN:                
     cmp     [ebp+var_8], 64h 
     jg      short loc_401050
     mov     eax, [ebp+var_4]
     add     eax, [ebp+var_8]
     mov     [ebp+var_4], eax
     mov     ecx, [ebp+var_8]
     add     ecx, 1
     mov     [ebp+var_8], ecx
     jmp     short loc_401036    ;往上跳,所以是 while
loc_401050:         ; WHILE_END:                              
     mov     edx, [ebp+var_4]
     push    edx
     push    offset Format   ; "%d\r\n"
     call    _printf

for

定式:

FOR_INIT: ; for 的 步长变量初始化

.....

jmp FOR_CMP

FOR_SETP: ;for 的步长

.....

FOR_CMP: ;for 的条件比较

.....

jmp FOR_END

FOR_BODY: ; 循环体内容

......

jmp FOR_STEP

FOR_END: ; for 结束

如果没有比较部分就是一个永真循环,就必须有 break

for 如果不写全就跟 while 一样

int main(int argc, char* argv[])
{
   int nSum = 0;

   for (int i = 1;
                 i<=100;
                 i++)
   {
      nSum = nSum + i;   
   } 
   printf("%d\r\n",nSum);
   return 0;
}
反汇编代码


    mov     [ebp+var_4], 0
        mov     [ebp+var_8], 1            ;初值部分 
    jmp     short loc_401041      ;跳转到比较
loc_401038:                                     ;步长部分
    mov     eax, [ebp+var_8]       
    add     eax, 1
    mov     [ebp+var_8], eax
loc_401041:                    比较部分         
    cmp     [ebp+var_8], 64h ; 'd'
    jg      short loc_401052       ;跳转到结束
                                           ;body部分
    mov     ecx, [ebp+var_4]
    add     ecx, [ebp+var_8]
    mov     [ebp+var_4], ecx
    jmp     short loc_401038    ;跳转到步长
loc_401052:                   ;for 结束            
    mov     edx, [ebp+var_4]
    push    edx
    push    offset Format   ; "%d\r\n"
    call    _printf

break, continue

者2个肯定 有 if ,不然他们下面的代码将无法执行(不可达分支)

break 是 跳转到 for 结尾

continue 是 跳转到 for 步长

int main(int argc, char* argv[])
{
   int nSum = 0;

   for (int i = 1;i<=100;i++)
   {
       if(sum > 50){
          break;
       }
       if(i % 6 == 5){
           continue;
       }
      nSum = nSum + i;   
   } 
   printf("%d\r\n",nSum);
   return 0;
}

反汇编代码

     mov     [ebp+var_4], 0
     mov     [ebp+var_8], 1
     jmp     short loc_401041
loc_401038:                           
     mov     eax, [ebp+var_8]
     add     eax, 1
     mov     [ebp+var_8], eax
loc_401041:                       
     cmp     [ebp+var_8], 64h ; 'd'
     jg      short loc_40106C
     cmp     [ebp+var_4], 32h ; 
     jle     short loc_40104F
     jmp     short loc_40106C     //跳到  for 结尾
loc_40104F:                         
     mov     eax, [ebp+var_8]
     cdq
     mov     ecx, 6
     idiv    ecx
     cmp     edx, 5
     jnz     short loc_401061
     jmp     short loc_401038     //跳到  for 步长
loc_401061:                          
     mov     edx, [ebp+var_4]
     add     edx, [ebp+var_8]
     mov     [ebp+var_4], edx
     jmp     short loc_401038
loc_40106C:                           
     mov     eax, [ebp+var_4]
     push    eax
     push    offset Format   ; "%d\r\n"
     call    _printf

有时候 continue 可以还原出 if else 的效果

while (i <= 100)
    {
         if( i % 4  == 0)
        {
              printf("\r\n");
              i++;
              continue ;
        }
        nSum = nSum + i;
        i++;
    }
=>  
    while (i <= 100)
    {
         if( i % 4  == 0)
        {
              printf("\r\n");
              i++;
        } 
        else 
        {
               nSum = nSum + i;
                i++;
        }
    }


RELEASE

release 会把 3种循环都优化成 do while 因为 do while 效率高

优化方法1 : 强度削弱

优化成 do while

do   while
int main(int argc, char* argv[])
{
   int nSum = 0;
   int i = 1;
   do
   {

      nSum = nSum + i;  
      i++;
   } while (i<=100);
   printf("%d\r\n",nSum);
   return 0;
}
反汇编代码:
     xor     ecx, ecx
     mov     eax, 1
loc_401007:                          
     add     ecx, eax
     inc     eax
     cmp     eax, 64h ; 'd'
     jle     short loc_401007
     push    ecx
     push    offset aD       ; "%d\r\n"
     call    sub_401020
     add     esp, 8
     xor     eax, eax
     retn


WHILE
int main(int argc, char* argv[])
{
   int nSum = 0;
   int i = 1;
   while (i<=100)
   {

      nSum = nSum + i;  
      i++;
   } 
   printf("%d\r\n",nSum);
   return 0;
}
反汇编代码
     xor     ecx, ecx
     mov     eax, 1
loc_401007:                           
     add     ecx, eax
     inc     eax
     cmp     eax, 64h ; 'd'
     jle     short loc_401007
     push    ecx
     push    offset aD       ; "%d\r\n"
     call    sub_401020
     add     esp, 8
     xor     eax, eax
     retn

FOR
int main(int argc, char* argv[])
{
   int nSum = 0;  
   for (int i = 1;i<=100;i++)
   {

      nSum = nSum + i;   
   } 
   printf("%d\r\n",nSum);
   return 0;
}

反汇编代码:
     xor     ecx, ecx
     mov     eax, 1
loc_401007:                           
     add     ecx, eax
     inc     eax
     cmp     eax, 64h ; 'd'
     jle     short loc_401007    ;循环上跳
     push    ecx
     push    offset aD       ; "%d\r\n"
     call    sub_401020
     add     esp, 8
     xor     eax, eax
     retn
可以看出  3种循环的反汇编代码是一样的,但是 do  while 至少会跑一次 ,跟 for 和 while 是不等价的,上面等价是因为常量传播了,   1 <= 100 ,所以编译器知道至少得跑一次

   while (i<=argc)
   {
      nSum = nSum + i;  
       i++;
   } 
就类似于
 if ( i<=argc )
{
        do
        {
        }  while   (i<=argc);
}
这样就避免了 第一次的问题,这样虽然看起来是两跳,但是 在循环体内部就只有 1跳了

碰到这种定式是,不能直接还原成   while  或者 for  循环,必须先判断  2次 比较有没有相关性,即 是不是相同的变量比较, 如果有相关性,还原成 for  或 while 就看自己的选择

GCC 或者其他编译器

也是把for 或 while 优化成 do while ,但是比较粗暴

jmp LOOP_CMP

LOOP_BEGIN:

......
......

LOOP_CMP:

jxx     LOOP_BEGIN

LOOP_END:

强度削弱

int main(int argc, char* argv[])
{
   int nSum = 0;
   int i = 1;
   while (i <= argc)
   {

      nSum =  i * argc;  
      i++;
   } 
   printf("%d\r\n",nSum);
   return 0;
}
反汇编代码:
     mov     ecx, [esp+argc]
     xor     edx, edx
     cmp     ecx, 1
     jl      short loc_401018
     push    esi
     mov     eax, ecx
     mov     esi, ecx
loc_401010:                          
     mov     edx, eax
     add     eax, ecx
     dec     esi
     jnz     short loc_401010
     pop     esi
loc_401018:                           
     push    edx
     push    offset aD       ; "%d\r\n"
     call    sub_401030
     add     esp, 8
     xor     eax, eax
     retn
分析:    上面可以看到.循环体里面并没有乘法,
那是因为编译器发现     nSum =  i * argc;      =>      nSum   =   nSum + argc; 因此把乘法转成了 加法
如果  i 不是 1开始, 只需要 最开始    nSum = i  *  argc;  循环里面再做 ++  ,这种可以直接按编译器的优化

优化方法二: 代码外提

int main(int argc, char* argv[])
{
   int nSum = 0;
   int i = 1;
   while (i<=argc / 7)
   {
      nSum = nSum + i;  
      i++;
   } 
   printf("%d\r\n",nSum);
   return 0;
}

反汇编代码:

     push    esi
     mov     esi, [esp+4+argc]
        ;除法
     mov     eax, 92492493h
     push    edi
     imul    esi
     add     edx, esi
     mov     ecx, 1
     sar     edx, 2
     mov     eax, edx
     xor     edi, edi
     shr     eax, 1Fh
     add     edx, eax
     cmp     edx, ecx
     jl      short loc_40102B
loc_401024:                      ;循环体     
     add     edi, ecx
     inc     ecx
     cmp     ecx, edx
     jle     short loc_401024
loc_40102B:                          
     push    edi
     push    offset aD       ; "%d\r\n"
     call    printf
     add     esp, 8
     xor     eax, eax
     pop     edi
     pop     esi
     retn
分析: 可以看到,编译器把除法提到 循环体外面去了,循环体内部并没有做除法

优化方法三:减少循环次数

高版本才有,低版本是没有的

int main(int argc, char* argv[])
{
    int nSum = 0;
    int i = 1;
    while (i <= 100)
    {
        nSum = nSum + i;
        i++;
    }
    printf("%d\r\n", nSum);
    return 0;
}
反汇编代码:

    push    esi
    push    edi
    xor     ecx, ecx
    xor     edx, edx
    xor     esi, esi
    mov     eax, 1
    xor     edi, edi
    nop          ; 这里也可以是等价 nop的指令,让循环的首地址在  %16 = 0 的位置
loc_401050:                           
    inc     edi
    add     esi, 2
    add     edx, 3
       ; 分成四段相加
    add     ecx, eax
    add     edi, eax
    add     esi, eax
    add     edx, eax
    add     eax, 4         ;每次循环  + 4
    cmp     eax, 64h ; 'd'
    jle     short loc_401050
       ;在把四段的值加在一起
    lea     eax, [edx+esi]
    add     eax, edi
    add     ecx, eax

    push    ecx
    push    offset _Format  ; "%d\r\n"
    call    _printf

分析:
   while (i <= 100)
    {
        nSum = nSum + i;
        i++;
    }
=>
 while (i <= 100)
    {
        nSum = nSum + i;
        nSum = nSum + i+1;
        nSum = nSum + i+2;
        nSum = nSum + i+3;
        i= i + 4;
    }
这样 本来要循环 100 次 ,经过 上面处理, 就只需要循环 25 次
每轮循环 加多少次 由编译器  和 循环终值  决定 

image.png

nop 等价的指令有很多 mov eax,eax xchg eax,eax 等等

让循环首地址 在 %16 = 0 的位置 ,这样访问更快,因此内存寻址更方便

整形数组寻址是,如果地址不是 四的倍数 ,那么 取值是就要取相邻2个地址的值,通过位运算 获得 需要的值

例如 : 00400002 地址的值 ,需要 00400000 的 后两位 + 00400004 的 前2位的值 ,因此需要先取出 00400000 和 00400004 处地址的值,在通过位运算得到我们想要的值 , 但是如果在 % 4 = 0的 地址,就只需要取一次,就可以减少开销

数组寻址公式: (int) ary + sizeof(type)*n

管理单位带来的有点: 在存储地址的时候可以忽略低位, 例如,保存一个32位的地址需要32跟地址线,四字节对齐值后就只需要 30 根地址线 ,(低2位是0), 另外2根地址线用来表达地址权限了,(0环 或者 3环)

0

主题

74

回帖

124

积分

注册会员

积分
124
发表于 6 天前 | 显示全部楼层
谢谢分享支持楼主
回复

使用道具 举报

0

主题

6

回帖

11

积分

新手上路

积分
11
发表于 6 天前 | 显示全部楼层
感谢分享,学习了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|断点社区 |网站地图

GMT+8, 2025-5-16 20:19 , Processed in 0.120451 second(s), 23 queries , Yac On.

Powered by XiunoBBS

Copyright © 2001-2025, 断点社区.

快速回复 返回顶部 返回列表