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

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

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

12.12 Do-While 循环结构优化

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

  .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循环。

  .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。

  .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寄存器内的次数作为循环终止条件,每次读入会自动递减,具体汇编代码如下。

  .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中的循环计数该如何处理,通常会把外层循环计数保存在栈中,这是非常的理想的,保存在一个变量内也勉强凑活,只是效率上没有直接压入栈中高。

  .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用于循环输出排序后的结果。

  .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。如果找到了满足条件的数组元素,则输出它和下一个数组元素的值,并跳出循环。如果循环结束都没有找到符合条件的数组元素,则直接退出程序。这段代码展示了如何使用循环和条件判断对数组进行遍历和筛选。

#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元素,并跳转到循环开头继续查找。

  .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函数进行输出。这段代码展示了如何使用循环结构遍历数组元素。

#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寄存器则只用做寻址因子使用。

  .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的三个数字序列都被产生出来为止。

#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寄存器内,以此来保证循环次数不冲突。

  .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循环结构,汇编代码如下所示,仔细看完全能看懂的。

  .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嵌套循环体的仿写就实现了,多说一句,在仿写时一定要注意次序跟规律谨慎些,写出来并不难。

  .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函数,在已知数组中查找指定值并输出其在数组中的索引。

#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;
}

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

  .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
posted @ 2023-08-24 09:33  lyshark  阅读(469)  评论(0编辑  收藏  举报

loading... | loading...
博客园 - 开发者的网上家园