汇编语言与递归无关,由于 C 语言以及调用约定和实现,它恰好可以工作。只需在汇编程序中实现 C 而不关心递归。我想我在一个宠物项目http://github.com/dwelch67/lsasim上谈到了这一点,除非我把它改掉,最后一课是手动将 C 中的递归转换为汇编程序。它不是 mips,所以不用担心这是一个家庭作业问题。
无论如何,开始的关键是简单地在汇编中实现 C。
例如,您有一个带有输入参数的函数。
int fix(int i, int x)
您将需要声明自己的调用约定或使用现有的约定,要实现 C,这意味着您需要一些输入参数的位置,将它们压入堆栈或将它们放入寄存器中。假设没有优化,您将在整个函数中保留这些变量并在最后进行清理。因此,如果您需要在代码中的任何位置调用 ANY 函数(递归,调用相同的函数,是 ANY 的一个非常小的子集,但属于该类别并且不是特殊的),您需要保留这些变量。如果调用约定将它们带入堆栈,那么您已经完成,如果调用约定将它们带入寄存器,那么您需要在调用之前保留它们并在调用之后恢复
push i
push x
implement call to function
pop x
pop i
并继续执行该功能。
就是这样,其余的将自行处理。
如果您碰巧注意到您作为示例创建的函数,在调用此函数中的函数后没有需要保留输入变量的路径。并且输入变量被修改并用作下一次调用的输入。因此,对 C 代码实现的优化是不用担心保留这些变量。只需修改它们并传递它们。在调用约定中使用寄存器将是为此特定函数执行此操作的最简单方法。这是编译器在优化时无论如何都会做的事情(如果使用基于寄存器的调用约定,则不保留)。
如果这就是所谓的,您也可以进行尾部优化。通常在调用函数时,您使用指令通常执行的任何操作来执行“调用”,这与简单的跳转或分支不同,因为在某处保存了返回值。并且有某种返回函数可以撤消此操作并在调用后返回到指令。嵌套调用意味着嵌套返回值,跟踪所有的返回值。尽管在这种情况下以及在其他情况下,您对函数的执行路径所做的最后一件事是调用另一个函数,但您可以(取决于指令集)分支到该函数,而不必嵌套另一组返回值。以 arm 指令集为例:
一些代码调用第一个函数:
bl firstfun:
在 arm bl 表示分支链接。寄存器 14 将填入返回值,程序计数器将填入函数 firstfun 的地址。
通常,如果您需要从函数调用函数,则需要保存 r14 以便可以从该函数返回,而无需进行尾部优化:
firstfun:
...
push {r14}
bl secondfun
pop {r14}
bx r14
...
secondfun:
bx r14
bx lr 只是表示分支到 r14 中的内容,在这种情况下是返回。优化看起来像这样,重要的是要注意,在第一个函数中,对第二个函数的调用是您在从第一个函数返回之前所做的最后一件事。这是此优化的关键。
firstfun:
...
b secondfun
...
secondfun:
bx r14
b 仅表示分支,而不是分支链接仅修改 pc 并且不修改 r14 或任何其他寄存器或堆栈。两个实现的执行在功能上是相同的,外部函数调用 firstfun 并且在正确的执行路径中有一个返回 (bx r14)。
其他人指出,由于您将原始调用者返回为零,因此该代码可能会完全优化自身。
fix:
return 0