如何使用循环终止递归函数?

时间:2019-05-14 09:14:40

标签: assembly masm irvine32

我正在尝试编写一个使用循环而不是使用条件循环的程序,如何防止使用循环作为“中断”的调用无限运行?

我知道循环将自动使用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,所以打印910),但是,我得到了10 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14...(无限运行)

1 个答案:

答案 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%。