循环语句(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) { 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 }; 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