我正在尝试编写一个使用循环而不是使用条件循环的程序,如何防止使用循环作为“中断”的调用无限运行?
我知道循环将自动使用ecx作为计数器,当ecx为0时,循环将终止,但是由于循环内的递归调用,我的程序似乎无限运行。我还尝试了jmp指令,并将循环多次定位在其他位置,但我仍然可以无限期地运行该程序。
.data
count DWORD ? ;the counter for the # of times the loop ran
userVal DWORD ? ;the # of times the loop will run according to the user
.code
main PROC
mov count, 0
call ReadDec ;read userinput for userVal, and stores it in ecx as counter
mov userVal, eax
mov ecx, userVal
mov eax,0
call recur ;call the recursion procedure
;call DumpRegs ;for showing registers
exit
main ENDP
recur PROC USES ecx eax ;the recursion Proc(objective: terminate the procedure with decrementing ecx
; so the recursion will run ecx # of times)
mov eax, count ;store the count (starts with 0)
call WriteInt ;prints the count in consol
inc count ;increment count everytime recursion is called
L2: ;the loop
call recur ; recursive call
loop L2
ret
recur ENDP
END main
预期输出为10 0 1 2 3 4 5 6 7 8 9
(由于递归过程应运行10次,0
是userVal,所以打印9
至10
),但是,我得到了10 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14...
(无限运行)
答案 0 :(得分:1)
...编写一个使用
loop
进行递归过程而不使用条件语句的程序...
我发现这是一个有趣的挑战,因为它需要一些开箱即用的思维。它可能具有零实际用途,但仍具有一些概念验证的优点。
loop
指令以带符号的8位位移编码,这意味着条件跳转可以后退和前进!在今天仍然使用loop
的大多数(如果不是全部)情况下,我们只会向后跳。
将loop
指令放在最前面,看起来很不自然,但是效果很好。
下面的代码分为两个阶段
将inc count
放在call WriteInt
之前将call WriteInt
暴露为tail call,因此我可以将其替换为jmp WriteInt
。
当生产阶段开始时,ECX
将为0。因此,我在内存中使用了ECX
寄存器,而不是在内存中使用 count 变量。目的。
该代码可以防止进入无限循环并通过jecxz Done
指令引起堆栈溢出。
jmp Start
; ----------------------
Recur:
loop .a ; Jumps N-1 times
jmp .b ; Jumps 1 time
.a: call Recur
.b: mov eax, ecx
inc ecx
jmp WriteInt ; Returns N times
; ----------------------
Start:
call ReadDec ; -> EAX (valid input is assumed)
mov ecx, eax ; The unsigned number N is [0,4GB-1]
jecxz Done ; In case N == 0
call Recur
Done:
有趣的是,以普通方式使用loop
编写此代码同样容易,因此可以向后跳。但是,它需要在计数器上增加一个额外的增量,并且会跳得更多(请参见下面的比较)。
jmp Start
; ----------------------
Recur:
jmp .b ; Jumps N+1 times
.a: call Recur
mov eax, ecx
inc ecx
jmp WriteInt ; Returns N times
.b: loop .a ; Jumps N times
ret ; Returns 1 time
; ----------------------
Start:
call ReadDec ; -> EAX (valid input is assumed)
mov ecx, eax ; The unsigned number N is [0,4GB-1]
jecxz Done ; In case N == 0
inc ecx
jz Done ; IN case N == 4GB-1 (stack overflow for sure!)
call Recur
Done:
下面是我比较这两种方法的方式。出于明显的原因,我删除了对 WriteInt 的调用...
LOOP FORWARD LOOP BACKWARD
jmp Start jmp Start
; ---------------------- ; ----------------------
Recur: Recur:
loop .a jmp .b
jmp .b .a: call Recur
.a: call Recur mov eax, ecx
.b: mov eax, ecx inc ecx
inc ecx ret
ret .b: loop .a
ret
; ---------------------- ; ----------------------
Start: Start:
mov ecx, 25000 mov ecx, 25000
call Recur inc ecx
call Recur
左侧的代码段执行时间为282微秒,右侧的代码段执行时间为314微秒。慢了11%。