C ++使用64位变量进行尾递归

时间:2015-03-07 20:05:43

标签: c++ algorithm visual-studio assembly recursion

我已经编写了一个简单的Fibonacci函数作为C ++中的练习(使用Visual Studio)来测试Tail Recursion并查看它是如何工作的。

这是代码:

int fib_tail(int n, int res, int next) {
  if (n == 0) {
    return res;
  }
  return fib_tail(n - 1, next, res + next);
}

int main()
{
  fib_tail(10,0,1); //Tail Recursion works
}

当我使用Release模式编译时,尽管有一个调用,我仍然使用JMP指令看到了优化的程序集。所以我的结论是:尾递归有效。见下图:

Tail Recursion

我想通过增加Fibonacci函数中的输入变量 n 来进行一些性能测试。然后我选择将函数中使用的变量类型从int更改为unsigned long long。然后我通过了一个很大的数字:10e + 08

这是新功能:

typedef  unsigned long long ULONG64;

ULONG64 fib_tail(ULONG64 n, ULONG64 res, ULONG64 next) {
   if (n == 0) {
     return res;
   }
   return fib_tail(n - 1, next, res + next);
}

int main()
{
  fib_tail(10e+9,0,1); //Tail recursion does not work
}

当我运行上面的代码时,我得到了一个堆栈溢出异常,这让我觉得尾递归不起作用。我看着集会,事实上我发现了这个:

Tail Recursion doesn't work

正如您现在看到的那样,有一个调用指令,而我只期待一个简单的 JMP 。我不明白使用8字节变量禁用尾递归的原因。为什么编译器在这种情况下不执行优化?

1 个答案:

答案 0 :(得分:9)

这是你必须要问那些为MS进行编译器优化的人的问题之一 - 实际上没有任何技术原因可以解决为什么任何返回类型应该防止尾递归这样的跳转 - 可能存在其他原因,如“代码太复杂,无法理解”或其他一些原因。

几周之后,clang 3.7清楚地表明了这一点:

_Z8fib_tailyyy:                         # @_Z8fib_tailyyy
    pushl   %ebp
    pushl   %ebx
    pushl   %edi
    pushl   %esi
    pushl   %eax
    movl    36(%esp), %ecx
    movl    32(%esp), %esi
    movl    28(%esp), %edi
    movl    24(%esp), %ebx
    movl    %ebx, %eax
    orl %edi, %eax
    je  .LBB0_1
    movl    44(%esp), %ebp
    movl    40(%esp), %eax
    movl    %eax, (%esp)            # 4-byte Spill
.LBB0_3:                                # %if.end
    movl    %ebp, %edx
    movl    (%esp), %eax            # 4-byte Reload
    addl    $-1, %ebx
    adcl    $-1, %edi
    addl    %eax, %esi
    adcl    %edx, %ecx
    movl    %ebx, %ebp
    orl %edi, %ebp
    movl    %esi, (%esp)            # 4-byte Spill
    movl    %ecx, %ebp
    movl    %eax, %esi
    movl    %edx, %ecx
    jne .LBB0_3
    jmp .LBB0_4
.LBB0_1:
    movl    %esi, %eax
    movl    %ecx, %edx
.LBB0_4:                                # %return
    addl    $4, %esp
    popl    %esi
    popl    %edi
    popl    %ebx
    popl    %ebp
    retl


main:                                   # @main
    subl    $28, %esp
    movl    $0, 20(%esp)
    movl    $1, 16(%esp)
    movl    $0, 12(%esp)
    movl    $0, 8(%esp)
    movl    $2, 4(%esp)
    movl    $1410065408, (%esp)     # imm = 0x540BE400
    calll   _Z8fib_tailyyy
    movl    %edx, f+4
    movl    %eax, f
    xorl    %eax, %eax
    addl    $28, %esp
    retl

同样适用于gcc 4.9.2,如果你给它-O2(但不是-O1,这是所有clang需要的)

(当然也是64位模式)