循环语句(While)一种基本控制结构,它允许程序在条件为真的情况下重复执行一段代码块,直到条件为假为止。循环语句在处理需要重复执行的任务时非常有用,它可以让程序更加高效地处理大量数据或者重复性操作。
一般来说,While循环由一个条件表达式、一个代码块组成。在每次循环迭代开始时,程序会首先检查条件表达式的值,如果为真,则执行代码块,然后再次检查条件表达式的值。只要条件表达式为真,循环就会一直继续执行;一旦条件表达式为假,循环将停止,程序继续执行循环之后的代码。
12.12 Do-While 循环结构优化
DO语句先执行循环体,后进行判断,如果通过则跳转到循环体首部继续执行,未通过则直接顺序向下走。DO循环效率最高,该循环在结构上非常精简,利用了程序执行时由低到高的特性,由于结构内只在结尾处做了判断,只使用了一条判断语句即实现了循环,因此已经无需在结构上进行任何优化了。
代码语言:javascript复制 .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循环。
代码语言:javascript复制 .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。
代码语言:javascript复制 .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寄存器内的次数作为循环终止条件,每次读入会自动递减,具体汇编代码如下。
代码语言:javascript复制 .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中的循环计数该如何处理,通常会把外层循环计数保存在栈中,这是非常的理想的,保存在一个变量内也勉强凑活,只是效率上没有直接压入栈中高。
代码语言:javascript复制 .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。如果找到了满足条件的数组元素,则输出它和下一个数组元素的值,并跳出循环。如果循环结束都没有找到符合条件的数组元素,则直接退出程序。这段代码展示了如何使用循环和条件判断对数组进行遍历和筛选。
代码语言:javascript复制#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函数进行输出。这段代码展示了如何使用循环结构遍历数组元素。
代码语言:javascript复制#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的三个数字序列都被产生出来为止。
代码语言:javascript复制#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嵌套循环体的仿写就实现了,多说一句,在仿写时一定要注意次序跟规律谨慎些,写出来并不难。
代码语言:javascript复制 .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函数,在已知数组中查找指定值并输出其在数组中的索引。
代码语言:javascript复制#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;
}
接着是尝试使用汇编实现这个查找逻辑,这段代码你一定可以看懂,细心些就好,我写的时候也思考了很长时间才写出来的。
代码语言:javascript复制 .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 许可协议。转载请注明出处!