【发布时间】:2019-12-05 00:27:59
【问题描述】:
我是这种汇编语言的新手,我尝试自己编写以下代码。问题是我的代码无法正确计算数字的阶乘,并且始终在终端中显示 1 作为输出。我想知道它不起作用的原因。
.text
mystring1: .asciz "Assignment 4: recursion\nType any number to calculate the factorial of that number:\n" # string for printing message
formatstr: .asciz "%ld" # format string for printing number
mystring2: .asciz "\n" # string for printing a new line
.global main # make the main label visible
main:
pushq %rbp # store the caller's base pointer
movq %rsp, %rbp # initialise the base pointer
movq $0, %rax # no vector registers in use for printf
movq $mystring1, %rdi # load address of a string
call printf # call the printf subroutine
call inout # call the inout subroutine
movq $0, %rax # no vector registers in use for printf
movq $mystring2, %rdi # load address of a string
call printf
jmp end
inout:
pushq %rbp # push the base pointer
movq %rsp, %rbp # copy the stack pointer to rbp
subq $16, %rsp # reserve stack space for variable
leaq -8(%rbp), %rsi # load address of stack variable in rsi
movq $formatstr, %rdi # load first argument of scanf
movq $0, %rax # no vector registers in use for scanf
call scanf # call scanf routine
movq -8(%rbp), %rsi # move the address of the variable to rsi
call factorial
movq $0, %rax # no vector registers in use for printf
movq $formatstr, %rdi # move the address formatstring to rdi
call printf # print the result
movq %rbp, %rsp # copy rbp to rsp
popq %rbp # pop rbp from the stack
ret # return from the subroutine
factorial:
cmpq $1, %rsi
jle factend
pushq %rbx
movq %rsi, %rbx
subq $1, %rsi
call factorial
mulq %rbx
popq %rbx
ret
factend:
movq $1, %rax
ret
end:
mov $0, %rdi # load program exit code
call exit # exit the program
我的代码的伪代码:
long rfact(long n)
{
long result;
if (n < = 1)
{
result = 1;
}
else
{
result = n * rfact(n - 1);
return result;
}
}
【问题讨论】:
-
您没有使用
mulq的高半 RDX 结果。使用更高效的 2 操作数imul %rbx, %rax。顺便说一句,x86-64 System V 调用约定传递 RDI 中的第一个整数/指针 arg。您已经为 args 到 printf 和 scanf 这样做了,所以您为自己的函数选择 RSI 很奇怪。 -
坦率地说,我还有很多东西要学,我只是随机使用 RSI,但仍然感谢您提供的信息
-
是的,这就是我教你的原因:P
-
好吧,既然你问了,哦,如果你关心效率的话,那就太多了。 xor-zeroing,RIP-relative LEA,(谷歌搜索或搜索 SO)和
mov $1, %eaxzero-隐式扩展到 RAX;无需在 64 位操作数大小上浪费代码大小。 (地址相同;如果您正在针对位置相关的 Linux 可执行文件进行优化,请使用mov $formatstr, %edi,而不是%rdi,当您不需要 RIP 相关的 LEA 将静态地址放入寄存器中时。@987654330 @ 占用更多空间并没有任何好处,GAS 不会为您做这种优化,即使是数字常量。) -
但就正确性和良好的风格而言,你在这里做得很好。例如在调用者中使用堆栈空间作为 scanf 暂存空间。跨
factorial保存/恢复 RBX 并使用它来保存本地变量是编译器会做的事情。哦,把你的字符串文字放在.section .rodata,代码放在.text。如果你好奇,把你的 C 放到 godbolt.org 中,看看它是如何用-O1或-O2编译的。 ...现在我很好奇:godbolt.org/z/YkxAYS 表明 GCC -O1 所做的事情与您所做的非常接近。 clang -O1 仍然将简单的递归优化为循环。
标签: recursion assembly x86-64 att