factorial
是一个全球标签,因此它可以受到符号插入. See Linux 上动态库的抱歉状态 https://www.macieira.org/blog/2012/01/sorry-state-of-dynamic-libraries-on-linux/。 (还,插入的一个例子malloc与 LD_PRELOAD http://jayconrod.com/posts/23/tutorial-function-interposition-in-linux, 还有一些docs https://docs.oracle.com/cd/E23824_01/html/819-0690/gejgf.html).
创建共享库时,目标call factorial
指令不被假定为factorial:
同一文件中定义的标签。那是because你用过.globl factorial
.
正如 Jester 指出的,您应该为call
目标,以便您可以保持全球factorial
name.
您可以创建一个更简单的“帮助器”函数,该函数使用自己的自定义调用约定,并且不会使用以下命令创建堆栈帧%rbp
对于递归部分,如果你愿意的话。 (但是在堆栈上获取 arg 对于 x86-64 来说已经是非标准的了)。
You could通过 PLT 调用或通过 GOT 内存间接调用,但不要这样做;你不希望每个人都有额外的开销call
,并且您不希望符号插入将非标准调用约定实现替换为传递第一个整数参数的普通实现%rdi
.
说到这里,在堆栈上传递 arg 是很慢的。您确实需要保存/恢复某些内容,除非您将递归重写为尾递归,例如factorial_helper(accumulator*n, n-1) https://stackoverflow.com/questions/15518882/how-exactly-does-tail-recursion-work。但你也不需要创建一个堆栈框架%rbp
每次。
在之前您没有维护 16 字节堆栈对齐call
,但是当调用自己不关心的私有函数时,您不需要它。
当然,如果您根本关心性能,那么您首先就不会使用递归实现,因为这样做的唯一原因是factorial
是作为一种学习练习。重写为尾递归允许您(或编译器(如果用 C 编写) https://stackoverflow.com/questions/15518882/how-exactly-does-tail-recursion-work)转动call
/ ret
into a jmp
,这当然会变成一个循环。
有关的:有哪些真正激发递归研究的好例子? https://cseducators.stackexchange.com/questions/4143/what-are-good-examples-that-actually-motivate-the-study-of-recursion/4361#4361。二叉树遍历或阿克曼函数以递归方式比迭代方式更容易实现,但是factorial
或斐波那契更难(就斐波那契而言,much慢点)。