欢迎光临散文网 会员登陆 & 注册

5.12 汇编语言:仿写While循环语句

2023-08-24 10:23 作者:bili_42682284418  | 我要投稿

循环语句(While)一种基本控制结构,它允许程序在条件为真的情况下重复执行一段代码块,直到条件为假为止。循环语句在处理需要重复执行的任务时非常有用,它可以让程序更加高效地处理大量数据或者重复性操作。


一般来说,While循环由一个条件表达式、一个代码块组成。在每次循环迭代开始时,程序会首先检查条件表达式的值,如果为真,则执行代码块,然后再次检查条件表达式的值。只要条件表达式为真,循环就会一直继续执行;一旦条件表达式为假,循环将停止,程序继续执行循环之后的代码。


### 12.12 Do-While 循环结构优化


DO语句先执行循环体,后进行判断,如果通过则跳转到循环体首部继续执行,未通过则直接顺序向下走。DO循环效率最高,该循环在结构上非常精简,利用了程序执行时由低到高的特性,由于结构内只在结尾处做了判断,只使用了一条判断语句即实现了循环,因此已经无需在结构上进行任何优化了。


```ASM

  .386p

  .model flat,stdcall

  option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


.data

  count DWORD ?

.code

  main PROC

    mov dword ptr ds:[count],0     ; 初始化循环次数

  S1:

    xor eax,eax                    ; 执行循环体

    mov eax,dword ptr ds:[count]   ; 取出计数器

    add eax,1                      ; 递增

    mov dword ptr ds:[count],eax   ; 回写

    

    cmp dword ptr ds:[count],10    ; 与10做对比

    jl S1                          ; 小于则继续循环


    int 3

    invoke ExitProcess,0

  main ENDP

END main

```


### 12.13 While 循环结构优化


While语句先判断循环条件,后执行循环体,由于需要判断,该循环的构建需要使用两个跳转语句方可实现。While循环结构的效率要比Do循环结构低,While循环结构先比较再循环,因此无法利用程序执行顺序来完成循环,又因为While循环结构使用了2个跳转指令,在程序流程上就弱于Do循环。


```ASM

  .386p

  .model flat,stdcall

  option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


.data

  count DWORD ?

.code

  main PROC

    mov dword ptr ds:[count],0            ; 设置while初始化

  S1:

    cmp dword ptr ds:[count],10           ; 设置最大循环数

    jge loop_end                          ; 判断是否循环结束

    

    xor eax,eax                           ; 执行循环体

    

    mov eax,dword ptr ds:[count]           ; 取出循环条件

    add eax,1                              ; 递增

    mov dword ptr ds:[count],eax           ; 写回

    jmp S1

  

  loop_end:

    int 3


    invoke ExitProcess,0

  main ENDP

END main

```

为了提升While循环执行效率,编译器通常会将其转换为对等的Do循环,如果循环无法转成对等的Do循环,则可使用单层IF结构内部嵌套Do循环的方式来实现,外层IF则用来判断Do循环是否执行,例如如下案例中,首先外层使用IF语句判断循环条件,该语句内部则嵌套一个Do循环,以此来将While转为Do。

```ASM

  .386p

  .model flat,stdcall

  option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


.data

  count DWORD ?

.code

  main PROC

    mov dword ptr ds:[count],0            ; 设置初始条件

    

    ; 初次判断条件是否满足

    cmp dword ptr ds:[count],10

    jge loop_end

  S1:

    ; 循环体内部语句

    xor eax,eax

    

    ; 递增

    add dword ptr ds:[count],1

    

    ; 比较条件是否满足

    cmp dword ptr ds:[count],10

    jl S1

    

  loop_end:

    int 3


    invoke ExitProcess,0

  main ENDP

END main

```


### 12.15 Loop 循环结构优化


上方提到过的三种循环模式都是通过跳转指令与计数器构建的,与这三者不同汇编中默认提供了loop指令,专门用来实现循环计数功能,由于是汇编指令,所以此loop语句必须读入ECX寄存器内的次数作为循环终止条件,每次读入会自动递减,具体汇编代码如下。

```ASM

  .386p

  .model flat,stdcall

  option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


.data

  count DWORD ?

  result DWORD ?

  ArrayDW DWORD 8,7,9,4,3,7,5,8,9,3,0h


.code

  main PROC

    ; 通过loop实现的单层循环体

    xor eax,eax                       ; 用于累加数据

    mov ecx,10                        ; 设置循环次数

  s1:

    mov dword ptr ds:[count],ecx      ; 将循环次数备份

    xor ecx,ecx                       ; 清空寄存器

    mov ecx,10

    add eax,ecx                       ; 结果想加到eax

    mov ecx,dword ptr ds:[count]      ; 恢复循环次数

    loop s1

    

    mov dword ptr ds:[result],eax     ; 获取相加后的结果

    

    invoke ExitProcess,0

  main ENDP

END main

```

如果是双层循环体,则在使用loop语句构建时,必须考虑外层ECX中的循环计数该如何处理,通常会把外层循环计数保存在栈中,这是非常的理想的,保存在一个变量内也勉强凑活,只是效率上没有直接压入栈中高。

```ASM

  .386p

  .model flat,stdcall

  option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


.data

  count DWORD ?

  result DWORD ?

  ArrayDW DWORD 8,7,9,4,3,7,5,8,9,3,0h


.code

  main PROC

    ; 通过loop实现嵌套循环体

    mov ecx,9                   ; 控制外层循环数

  s2:

    push ecx                    ; 保存外层循环数

    mov ecx,9                   ; 设置内层循环数

  s3:

    mov eax,dword ptr ds:[ArrayDW + ecx * 4]

    cmp eax,3                    ; 与3作比较大于则跳

    ja jump

    loop s3

  jump:

    xor eax,eax

    pop ecx

    loop s2

    

    invoke ExitProcess,0

  main ENDP

END main

```

运用Loop指令实现对数组`MyArray`的由大到小排序,其中`ArraySort`子过程用于由大到小排序,子过程`PrintArray`用于循环输出排序后的结果。

```ASM

  .386p

  .model flat,stdcall

  option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


include msvcrt.inc

includelib msvcrt.lib


.data

  MyArray DWORD 25,74,89,33,24,67,93,15,78,92

  Count DWORD 10

  PrCount DWORD ?

  szFmt BYTE '%d ',0dh,0ah,0


.code

  ; 数组排序函数

  ArraySort PROC

      mov ecx,dword ptr ds:[Count]      ; 获取到数组元素数

      dec ecx                           ; 数组减1

    L1:

      push ecx                          ; 入栈保存

      lea esi,dword ptr ds:[MyArray]    ; 得到数组基地址

      

    L2:

      mov eax,dword ptr ds:[esi]

      cmp eax,dword ptr ds:[esi + 4]    ; 比较第一个数组与第二个

      jg L3

      

      xchg eax,[esi + 4]                ; 交换数据

      mov [esi],eax

    L3:

      add esi,4

      loop L2

      

      pop ecx                           ; 弹出数据

      loop L1

      ret

  ArraySort ENDP

  

  ; 循环输出元素

  PrintArray PROC

      mov dword ptr ds:[PrCount],0            ; 初始化元素

      

    L1:

      mov ecx,dword ptr ds:[PrCount]          ; 获取循环次数

      cmp ecx,10                              ; 对比十次

      jge lop_end                             ; 判断是否可结束循环

      

      lea eax,dword ptr ds:[MyArray]          ; 获取数组基地址

      mov ebx,dword ptr ds:[eax + ecx * 4]    ; 比例因子寻址

      invoke crt_printf,addr szFmt,ebx

      

      mov ecx,dword ptr ds:[PrCount]          ; 取循环计数

      add ecx,1

      mov dword ptr ds:[PrCount],ecx          ; 递增后回写

      jmp L1

      

    lop_end:

      int 3

      ret


  PrintArray ENDP


  main PROC

    invoke ArraySort

    invoke PrintArray

  main ENDP

END main

```


### 12.16 仿写Do-While循环体


这段C++代码定义了一个包含10个元素的整型数组,然后在do-while循环中对数组进行遍历,并检查每一个数组元素是否满足下面的条件:它的值大于10并且下一个数组元素的值小于等于20。如果找到了满足条件的数组元素,则输出它和下一个数组元素的值,并跳出循环。如果循环结束都没有找到符合条件的数组元素,则直接退出程序。这段代码展示了如何使用循环和条件判断对数组进行遍历和筛选。


```C

#include <stdio.h>

#include <Windows.h>


int main(int argc, char *argv[])

{

  int Array[10] = { 56,78,33,45,78,90,32,15,56,67 };

  int index = 0;


  do

  {

    if (Array[index] > 10 && Array[index + 1] <= 20)

    {

      printf("array[1] => %d array[2] => %d \n", Array[index], Array[index + 1]);

      break;

    }


    index = index + 1;

  } while (index < 10);


  system("pause");

  return 0;

}

```

由于是自己仿写,所以此处我使用了For形式的循环模式,首先初始化`count=0`进入L1循环后先来判断数组中第一个元素是否小于10,接着通过`add eax,1`将比例因子向后移动4字节,继续比较第二个数值是否小于等于20,如果都存在则直接输出该结果,并通过`jmp lop_end`跳转到程序结尾,否则递增`count`元素,并跳转到循环开头继续查找。

```ASM

  .386p

  .model flat,stdcall

  option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


include msvcrt.inc

includelib msvcrt.lib


.data

  MyArray DWORD 56,78,33,45,78,90,32,15,56,67

  count DWORD ?

  szFmt BYTE 'array[1] => %d array[2] => %d ',0dh,0ah,0


.code

  main PROC

    mov dword ptr ds:[count],0                     ; int index = 0;


  L1:

    mov eax,dword ptr ds:[count]

    cmp dword ptr ds:[MyArray + eax * 4],10        ; Array[index] > 10

    jle L2

    

    mov eax,dword ptr ds:[count]

    add eax,1

    cmp dword ptr ds:[MyArray + eax * 4],20        ; Array[index + 1] <= 20

    jg L2

    

    mov esi,dword ptr ds:[MyArray + eax * 4 - 4]   ; esi = Array[index]

    mov edi,dword ptr ds:[MyArray + eax * 4]       ; edi = Array[index+1]

    invoke crt_printf,addr szFmt,esi,edi

    jmp lop_end                                    ; break


  L2:

    mov eax,dword ptr ds:[count]

    add eax,1                                      ; index = index + 1;

    mov dword ptr ds:[count],eax

    cmp dword ptr ds:[count],10                    ; index < 10

    jl L1


  lop_end:                                           ; break

    int 3

  main ENDP

END main

```


### 12.17 仿写While循环体


这段C++代码定义了一个包含10个元素的整型数组,然后在while循环中对数组进行遍历,输出每一个数组元素的值。循环使用一个count变量作为计数器,从0开始逐步增加,直到count的值等于数组元素的总数。在循环内部,它通过count变量访问数组元素,并将它们的值作为参数传递给printf函数进行输出。这段代码展示了如何使用循环结构遍历数组元素。


```C

#include <stdio.h>

#include <Windows.h>


int main(int argc,char *argv[])

{

  int Array[10] = { 1,2,3,4,5,6,7,8,9,10 };

  int count = 0;


  while (count < sizeof(Array) / sizeof(int))

  {

    printf("value = %d \n", Array[count]);

    count = count + 1;

  }

  return 0;

}

```

首先初始化部分,设置`ecx寄存器`为比例因子,进入循环体后,通过寻址公式`ds:[esi + ecx * 4]`实现对数组地址的递增输出,此代码中的`ds:[count]`只用于控制循环体循环次数,`ecx寄存器`则只用做寻址因子使用。

```ASM

  .386p

  .model flat,stdcall

  option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


include msvcrt.inc

includelib msvcrt.lib


.data

  MyArray DWORD 1,2,3,4,5,6,7,8,9,10

  count DWORD ?

  szFmt BYTE 'value = %d ',0dh,0ah,0

.code

  main PROC

    mov dword ptr ds:[count],0                 ; 初始化循环

    mov ecx,0                                  ; 设置循环计数(比例因子)


  S1:

    cmp dword ptr ds:[count],lengthof MyArray  ; 与数组总长度对比

    jge lop_end                                ; 是否结束

    

    lea esi,dword ptr ds:[MyArray]             ; 获取数组基地址

    mov ebx,dword ptr ds:[esi + ecx * 4]       ; 比例因子寻址

    invoke crt_printf,addr szFmt,ebx           ; 调用系统crt

    

    mov ecx,dword ptr ds:[count]

    add ecx,1                                   ; 计次循环递增

    mov dword ptr ds:[count],ecx

    jmp S1


  lop_end:

    int 3

    invoke ExitProcess,0

  main ENDP

END main

```


### 12.18 仿写While三层循环体


这段C++代码实现了一个三重循环,用于生成所有由1到4中不重复的三个数字组成的序列。在外层循环中,它使用变量x从1开始逐个增加,直到其值大于等于5。在中间循环中,它使用变量y从1开始逐个增加,直到其值大于等于5。在最内层循环中,它使用变量z从1开始逐个增加,直到其值大于等于5。然后它检查当前的x、y、z变量是否满足三个数不重复的条件,如果满足,则输出这三个数字,并进入第三个循环。循环结构使用变量z逐项增加,并在检查条件后继续下一个序列的生成。当z逐项增加完成后,中间循环使用变量y逐项增加。如此循环,直到所有由1到4的三个数字序列都被产生出来为止。


```C

#include <windows.h>

#include <stdio.h>


int main(int argc,char * argv[])

{

  int x=1, y=1, z=1;


  while (x < 5)

  {

    while (y < 5)

    {

      while (z < 5)

      {

        

        if (x != z && x != y && y != z)

        {

          printf("%d,%d,%d \n", x, y, z);

        }

        z = z + 1;

      }

      z = 1;

      y = y + 1;

    }

    y = 1;

    x = x + 1;

  }

  return 0;

}

```

由于这段C代码使用了`三层While`循环,其构建为汇编代码时稍有些难度,我们首先把外层框架构建好,先来构建一个`二层While循环`结构,如下汇编代码中,我们通过变量`x DWORD`控制外层循环次数,内层循环次数则使用`y DWORD`变量来控制,当每次需要修改或读取变量时,则通过`mov ecx,dword ptr ds:[x]`指令将计数次数读入到`ecx寄存器`内,以此来保证循环次数不冲突。

```ASM

  .386p

  .model flat,stdcall

  option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


include msvcrt.inc

includelib msvcrt.lib


.data

  x DWORD ?

  y DWORD ?

  szFmt BYTE '外层循环: %d ---> 内层循环:%d ',0dh,0ah,0


.code

  main PROC

    mov dword ptr ds:[x],1           ; x = 1

  

    ; 外层循环

  L1:

    mov ecx,dword ptr ds:[x]

    cmp ecx,5                        ; x < 5

    jge lop_end

    

    ; 内层循环

    mov dword ptr ds:[y],1           ; y = 1

    

  L2:

    mov ecx,dword ptr ds:[y]         ; ecx = y

    cmp ecx,5                        ; y < 5

    jge L3

    

    ; 循环过程执行(存放循环过程代码)

    mov esi,dword ptr ds:[x]

    mov edi,dword ptr ds:[y]

    invoke crt_printf,addr szFmt,esi,edi


    mov ecx,dword ptr ds:[y]

    add ecx,1                        ; y = y + 1

    mov dword ptr ds:[y],ecx

    jmp L2


  L3:

    mov ecx,dword ptr ds:[x]

    add ecx,1                        ; x = x + 1

    mov dword ptr ds:[x],ecx

    jmp L1

  

  lop_end:

    int 3 


  main ENDP

END main

```

既然二层结构可以被构建出来,那么我们利用这个原理,在二层基础之上增加一个`z DWORD`变量,用于对最内部的`While`语句进行计数,由此我们就可以构建出`三层While循环`结构,汇编代码如下所示,仔细看完全能看懂的。

```ASM

  .386p

  .model flat,stdcall

  option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


include msvcrt.inc

includelib msvcrt.lib


.data

  x DWORD ?

  y DWORD ?

  z DWORD ?

  szFmt BYTE '外层循环: %d ---> 中间层循环: %d ---> 内层循环: %d  ',0dh,0ah,0


.code

  main PROC

    mov dword ptr ds:[x],1           ; x = 1

  

    ; 外层循环

  L1: mov ecx,dword ptr ds:[x]

    cmp ecx,5                        ; x < 5

    jge lop_end

    

    ; 中间循环

    mov dword ptr ds:[y],1           ; y = 1

  L2: mov ecx,dword ptr ds:[y]         ; ecx = y

    cmp ecx,5                        ; y < 5

    jge L3                           ; 大于跳到最外层


    ; 内层循环

    mov dword ptr ds:[z],1           ; z = 1

    

  L5: mov ecx,dword ptr ds:[z]

    cmp ecx,5                        ; z < 5

    jge L4                           ; 大于跳到中间层

    

    ; 三层循环框架

    mov eax,dword ptr ds:[x]

    mov ebx,dword ptr ds:[y]

    mov ecx,dword ptr ds:[z]

    invoke crt_printf,addr szFmt,eax,ebx,ecx

    

    mov ecx,dword ptr ds:[z]

    add ecx,1                         ; z = z + 1

    mov dword ptr ds:[z],ecx

    

    jmp L5

    

  L4: mov ecx,dword ptr ds:[y]

    add ecx,1                        ; y = y + 1

    mov dword ptr ds:[y],ecx

    jmp L2


  L3: mov ecx,dword ptr ds:[x]

    add ecx,1                        ; x = x + 1

    mov dword ptr ds:[x],ecx

    jmp L1

  

  lop_end:

    int 3 


  main ENDP

END main

```

最后我们用上方三层结构作为框架使用,在其基础之上增加内部的IF判断功能实现,这样一来我们的三层While嵌套循环体的仿写就实现了,多说一句,在仿写时一定要注意次序跟规律谨慎些,写出来并不难。

```ASM

  .386p

  .model flat,stdcall

  option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


include msvcrt.inc

includelib msvcrt.lib


.data

  x DWORD ?

  y DWORD ?

  z DWORD ?

  szFmt BYTE '%d,%d,%d ',0dh,0ah,0


.code

  main PROC

    mov dword ptr ds:[x],1           ; x = 1

  

    ; 外层循环

  L1: mov ecx,dword ptr ds:[x]

    cmp ecx,5                        ; x < 5

    jge lop_end

    

    ; 中间循环

    mov dword ptr ds:[y],1           ; y = 1

  L2: mov ecx,dword ptr ds:[y]         ; ecx = y

    cmp ecx,5                        ; y < 5

    jge L3                           ; 大于跳到最外层


    ; 内层循环

    mov dword ptr ds:[z],1           ; z = 1

    

  L5: mov ecx,dword ptr ds:[z]

    cmp ecx,5                        ; z < 5

    jge L4                           ; 大于跳到中间层

    

    ; 三层循环框架

    ;mov eax,dword ptr ds:[x]

    ;mov ebx,dword ptr ds:[y]

    ;mov ecx,dword ptr ds:[z]

    ;invoke crt_printf,addr szFmt,eax,ebx,ecx

    

    ; 开始在框架中搞事情

    mov eax,dword ptr ds:[x]

    cmp eax,dword ptr ds:[z]

    je L6

    mov eax,dword ptr ds:[x]

    cmp eax,dword ptr ds:[y]

    je L6

    mov eax,dword ptr ds:[y]

    cmp eax,dword ptr ds:[z]

    je L6

    

    invoke crt_printf,addr szFmt,dword ptr ds:[x],dword ptr ds:[y],dword ptr ds:[z]

    

  L6: mov ecx,dword ptr ds:[z]

    add ecx,1                         ; z = z + 1

    mov dword ptr ds:[z],ecx

    

    jmp L5

    

  L4: mov ecx,dword ptr ds:[y]

    add ecx,1                        ; y = y + 1

    mov dword ptr ds:[y],ecx

    jmp L2


  L3: mov ecx,dword ptr ds:[x]

    add ecx,1                        ; x = x + 1

    mov dword ptr ds:[x],ecx

    jmp L1

  

  lop_end:

    int 3 


  main ENDP

END main

```


### 12.19 仿写While实现二分法


该C++代码实现了一个二分查找算法,用于在已排序的数组中查找指定值的位置。代码中定义了一个BinSearch函数,通过对传入数组进行二分查找,最终返回要查找的值在数组中的索引值。main函数调用了BinSearch函数,在已知数组中查找指定值并输出其在数组中的索引。


```C

#include <windows.h>

#include <stdio.h>


int BinSearch(int value[], const int SearchVal, int Count)

{

  int first = 0;

  int last = Count - 1;


  while (first <= last)

  {

    int mid = (last + first) / 2;      // 取中位数

    if (value[mid] < SearchVal)        // 中位数小于searchVal

    {                                  // 说明元素在后面

      first = mid + 1;

    }

    else if (value[mid] > SearchVal)

    {                                  // 否则说明元素在前

      last = mid - 1;

    }

    else

    { // 找到后返回中位数

      return mid;

    }

  }

  return -1;

}


int main(int argc, char *argv[])

{

  // 二分查找法,必须针对的是有序数组

  int Array[10] = { 1,2,3,4,5,6,7,8,9,10 };


  // 查找数组Array中索引7所在的下标

  int ret = BinSearch(Array, 7, 10);

  printf("数组下标: %d \n", ret);


  system("pause");

  return 0;

}

```

接着是尝试使用汇编实现这个查找逻辑,这段代码你一定可以看懂,细心些就好,我写的时候也思考了很长时间才写出来的。

```ASM

  .386p

  .model flat,stdcall

  option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


include msvcrt.inc

includelib msvcrt.lib


.data

  MyArray DWORD 1,2,3,4,5,6,7,8,9,10

  

  SearchVal DWORD 7

  Count DWORD 10

  

  first DWORD ?

  last DWORD ?

  mid DWORD ?

  szFmt BYTE '%d ',0dh,0ah,0


.code

  main PROC

    mov dword ptr ds:[first],0         ; first = 0;

    mov edi,dword ptr ds:[SearchVal]   ; 得到要查找的数

    lea ebx,dword ptr ds:[MyArray]     ; 得到数组基地址


    ; int last = Count - 1;

    mov eax,dword ptr ds:[Count]

    sub eax,1

    mov dword ptr ds:[last],eax

    

    ; while(first <=last)

  L1: mov ecx,dword ptr ds:[first]

    cmp ecx,dword ptr ds:[last]

    jg lop_end

    

    ; int mid = (last + first) / 2;

    mov eax,dword ptr ds:[last]

    add eax,dword ptr ds:[first]

    shr eax,1

    mov dword ptr ds:[mid],eax

    

    ; edx = value[mid]

    mov esi,dword ptr ds:[mid]

    shl esi,2

    mov edx,[ebx + esi]

    ;invoke crt_printf,addr szFmt,edx


    ; if(edx < SearchVal(edi))

    cmp edx,edi

    jge L2

    ; first = mid + 1;

    mov eax,dword ptr ds:[mid]

    add eax,1

    mov dword ptr ds:[first],eax

    jmp L1

  L2:

    ; else if (value[mid] > searchVal)

    cmp edx,edi

    jle L3

    ; last = mid - 1;

    mov eax,dword ptr ds:[mid]

    sub eax,1

    mov dword ptr ds:[last],eax

    jmp L1

  

  L3: ; else

    mov eax,dword ptr ds:[mid]

    invoke crt_printf,addr szFmt,eax

    jmp lop_end

    jmp L1

  lop_end:

    mov eax,-1

    int 3


  main ENDP

END main

```


本文作者: 王瑞

本文链接: https://www.lyshark.com/post/910678b8.html

版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!


5.12 汇编语言:仿写While循环语句的评论 (共 条)

分享到微博请遵守国家法律