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