MASM是否可以在没有条件语句的情况下创建递归函数?

时间:2018-04-12 04:02:45

标签: assembly masm

对于我的汇编编程类中的赋值,我们在下面给出了问题。我尝试用几种不同的方式解决它,但我只能想到使用条件语句的解决方案。我没有在网上或班上其他人找到答案。来自c ++ / javascript风格的语言我理解必须有一个基本语句(否则该函数可以永远调用自己)。这不需要条件陈述吗?

这是问题所在:

**直接递归是一个过程调用自身时使用的术语。您不希望让过程永远自行调用,因为运行时堆栈会填满。您需要以某种方式限制递归。

编写一个调用递归过程的MASM程序。标记程序recProc。在此过程中,将1添加到计数器,以便您可以验证它执行的次数。使用调试器运行程序,并在程序结束时检查计数器的值。在ECX中放一个数字,指定允许递归继续的次数。

  

仅使用LOOP指令而不使用其他其他条件   语句。

为递归过程找到一种方法来调用自己固定的次数。

使用过程时,需要确保保留寄存器和标志。你需要使用PUSHFD / POPFD和PUSHAD / POPAD。**

2 个答案:

答案 0 :(得分:1)

loop is exactly like dec ecx / jnz(除了地址大小前缀的怪异和being much slower on most CPUs)。

它的分支位移范围是标准rel8 [-128 .. +127],因此不要求它用于后向分支

因此,您可以将其用作条件,甚至不跳过任何箍,只需使用它来跳过递归基本情况的代码(可能只是ret

; first arg in ECX = recursion count
my_func:
  loop  @keep_descending
  ;; fall-through path: ecx was 1 before loop, and is now 0
  ... do something here ...
  ret

@keep_descending:
  ; ecx has been decremented by 1
  push   ebx           ; save a call-preserved reg; use it to save state across the recursive call

  ... main body of function here ...

  call   my_func       ; clobbers ecx; save it if needed

  ... more stuff
  ; If your function way was tail-recursive, you should have just made a loop

  lea    eax, [edx+ecx]    ; return in EAX
  pop    ebx
  ret

如果您的递归终止条件不是下降,则很容易滥用loop将其用作通用if (n != 0)而不修改n 计数器。

  inc   ecx
  loop  ecx_was_non_zero
  ;; fall-through path: ecx was zero before inc, and is now zero again

这比test ecx,ecx / jnz短1个字节,因此对a code-golf GCD loop很有用(唯一重要的是代码大小,而不是性能)。

  

使用过程时,需要确保保留寄存器和标志。你需要使用PUSHFD / POPFD和PUSHAD / POPAD。

这是一个糟糕的调用约定,它会对你调用的每个函数施加大量的工作。允许函数使用clobber标志和eax / ecx / edx是完全正常的,因此他们可以使用一些寄存器而不保存/恢复。

POPAD使用起来非常不方便,因为它会覆盖所有寄存器,包括你想放置返回值的EAX。因此,您必须在POPAD周围保存/恢复EAX,或者必须将结果存储到堆栈的正确位置,以便POPAD将其加载到EAX中。

此外,可以在不使用pushad / popad的情况下保持寄存器和FLAGS不被修改,因此该语句为false。除非您将其视为单独的要求,否则也会以loop要求的方式给您带来不便。

  

MASM是否可以在没有条件语句的情况下创建递归函数?

这是一个不同的问题,答案是肯定的。您的选项包括自修改代码。 (有关示例,请参阅The Story of Mel。在您的情况下,您可能会在函数的开头将call指令覆盖为nop或其他内容。

您可以使用cmov无分支地执行此操作,以获取指向该指令或指向寄存器的无害位置的指针。或者你可能会调用cmov条件指令。它不是一个分支,但它在名称中有条件。无论如何,自修改代码对于现代CPU的性能来说是可怕的,所以你也不应该使用它。

但是,如果您只想提出滥用loop等愚蠢的计算机技巧,那么您还应该考虑自修改代码,指针查找表或其他影响流量控制的方法而无需标准条件指令,如jcc

答案 1 :(得分:0)

loop本身就是条件分支的东西,整个任务描述有点......呃...我的口味。嗯...

考虑到任务描述和最小的努力,我可能会产生这个(没有调试它,因为我没有MASM或任何能够运行MASM的操作系统,所以自己修复语法问题,但原则应该是从评论中清楚)

; in data section
counter DWORD 0

; in code section

; recursive function to call itself recursively ECX-1 many times
; ECX should be above zero (for zero it will end with 4 billions
; of recursive depth exhausting stack memory quickly)
recProc PROC
    ; preserve flags and all register values
    pushfd
    push   ecx      ; only ECX is modified in procedure body
    ; (no need for weapons of mass destruction like PUSHAD)

    ; don't call recursively with the same ECX, decrement it first
    jmp    recProc_test_if_call_is_needed

    ; main loop calling recProc ECX-1 many times
recProc_loop:
    call   recProc
recProc_test_if_call_is_needed:
    ; count every iteration, even ones not leading to recursion
    inc    DWORD PTR [counter]
    loop   recProc_loop

    ; restore all registers and flags
    pop    ecx
    popfd
    ret
ENDP

如果我在头脑中正确调试它,那么这应该在ECX=3中生成[counter] == 6参数值。 (整体[counter] == Σi,i = 1,..,原始ecx

编辑:

关于主题中的一般问题“是否可以创建没有条件语句的递归函数” ...

好吧,递归函数必须调用自身,否则它不是递归函数,所以1)函数体中的某个地方是自调用。

2)如果没有影响代码流的条件语句,该函数将始终以相同的方式运行(控制流方式),即进行自我调用。

1 + 2 =>无限循环。

你可以争论什么是条件语句,例如你可以创建内部函数jump,它计算目标地址作为数学表达式,有时跳转到包含自调用的代码路径,有时跳转到不包含调用的代码路径,在某些条件下的递归终端,但没有使用明确的Jccloop条件分支,但对我来说,即使它有资格作为“条件”语句。

所以简单的答案是:不,这是不可能的。

但是在你的任务中,这是有可能的,因为你得到了loop指令。 BTW不要在“生产”代码中使用loop指令:Why is the loop instruction slow? ...同样适用于pushad/popad(这也是一个可怕的想法 - 特别是在递归函数中,因为这意味着递归的深度将被用于保存不会改变的寄存器的浪费堆栈空间严重限制,但即使在非递归调用中,99%的情况下更好(性能明智)仅保存特定寄存器,而不是使用pushad/popad ),并在调用之间保留标志也是荒谬的。这就是我找到你的任务描述“meh”的原因,因为它实际上强制你编写愚蠢的代码。 :/