为什么这个scala tail递归函数会抛出java.lang.StackOverflowError

时间:2014-08-27 05:12:52

标签: scala tail-recursion stack-overflow

以下简单函数是尾递归。但是,它仍会抛出java.lang.StackOverflowError。谁能帮助我理解为什么我会这样做?

我已尝试使用包含数百万个项目的列表的尾递归函数,但从未得到StackOverflowError。或者这个函数尾递归不是吗?

def recursion(i: Int): Int = {
  if (i == 20000) i
  else {
    recursion(i + 1)
  }
}

recursion(0)

更新:添加@tailrec确实解决了问题,即使我将限制增加到2147483647.感谢@tiran的回答

2 个答案:

答案 0 :(得分:3)

该函数是尾递归的,实际上编译器会按预期对其进行优化。您的示例在我的scala 2.11.2 REPL中运行得很好(我添加了几个零,只是为了更好地证明这一点)

scala> def recursion(i: Int): Int = {
     |   if (i == 2000000) i
     |   else {
     |     recursion(i + 1)
     | }
     | }
recursion: (i: Int)Int

scala> recursion(0)
res0: Int = 2000000

关于@tailrec,它无法解决"任何东西。 @tailrec是一个注释,如果无法对带注释的函数执行尾调用优化(TCO),则会触发编译时错误。

因此,无论@tailrec注释如何,都会尽可能地执行TCO。

由于您的问题只是重新编译,您的IDE可能处于某种脏状态,编辑只是触发了一个干净的编译。代码从一开始就很好。

答案 1 :(得分:1)

在每种语言中,由于存在本地差异,每次调用函数调用时,都会在名为" stack"的内存区域上创建新的分配。存储有关函数的信息,例如参数和返回地址。当函数返回时,取消分配相应的堆栈块并返回给调用者。

假设您有以下功能:

def f(i:Integer) = {
  if (i <= 0) 0
 else f(i-1)
}

如果你打电话给f(3),你会得到:

f(3) - &gt;电话 - &gt; f(2) - &gt;电话 - &gt; f(1) - &gt;电话 - &gt; F(0)

和你的堆栈,当它处于最大高度时,看起来像:

stack(f(0))
stack(f(1))
stack(f(2))
stack(f(3))

一旦f(0)调用返回,它的块就会被释放,顺序来说,f(1),f(2),f(3)中的块也会被释放。

程序堆栈在任何时候都可以拥有的最大高度是有限制的。你的函数递归20000次,这超过了这个限制,所以JVM会生气并抛出异常。

添加@tailrec告诉计算机将函数编译为尾递归,即(或多或少)在不分配堆栈空间的while循环中转换它,例如:

def recursion(i:Int) = {
  while(i<20000) {
    i++
  }
  i
}

因此,您不会破坏堆栈上限:)

编辑:正如其他人所指出的那样,@ tailrec并没有告诉编译器执行TCO,但是如果不能对该方法执行TCO,则要求编译器失败,从而保证该方法的TCO如果编译成功。