JavaScript递归堆栈溢出

5
有人能解释一下为什么下面的结果不同吗?
// test one
function computeMaxCallStackSize() {
    try {
        return computeMaxCallStackSize() + 1;
    } catch (e) {
        return 1;
    }
}

console.log(computeMaxCallStackSize()); 

结果为17958

// test two
    function computeMaxCallStackSize() {
        try {
            return 1 + computeMaxCallStackSize();
        } catch (e) {
            return 1;
        }
    }

    console.log(computeMaxCallStackSize());

结果是15714

当函数'computeMaxCallStackSize'的位置不同时,结果也会不同。原因是什么?非常感谢!

运行环境: node.js v6.9.1 操作系统:Win7


可能是在早期版本的v8中出现了一个bug,在7.3.0 darwin x64上结果是相同的。 - georg
这是真的吗?我想稍后尝试一下。 - myzhou
你尝试过交换这两个测试案例吗?很多时候问题不在于代码,而是是否先执行了它。 - Bergi
实际上,我在Linux x86上尝试过了,但仍然得到了不同的结果。 - myzhou
1个回答

2
在这里,导致问题的不是位置,而是执行顺序。在第一个函数中,该语句是...
 return computeMaxCallStackSize() + 1;

调用increment函数并加1。
return 1 + computeMaxCallStackSize();

如果你将两个return语句视为相同的,则会导致相同的值。在后一个中,由于数字是第一个,因此与第一个相比,js调用堆栈更容易超出溢出。调用堆栈值取决于执行顺序,如在第二个中,如果您改变顺序,则递归发生较晚,您会获得较低的值。
您还可以通过添加一些console.log()或本地变量来检查调用堆栈,随着执行语句的增加,调用堆栈将逐渐减少。
如果您在两个中尝试computeMaxCallStackSize() + 1;,则将获得相同的值。

不确定您所说的“先调用增量,然后再加1”是什么意思 - 两个代码片段都会先调用computeMaxCallStackSize,然后再将值加1 - Bergi
是的,但是当您说函数被首先调用时,执行顺序会有所不同,这意味着可调用函数上的命令首先被推入堆栈中,您可以在loupe中检查执行顺序。 - Vinod Louis
具体执行顺序是什么?哪些“可调用函数上的命令”确切地被推送到堆栈中?通常情况下,命令根本不会被推送到堆栈中,只有状态会被推送。那么什么是loupe? - Bergi
对于 return computeMaxCallStackSize() + 1; 这样的函数,它会直接跳转到递归调用中,而不考虑加法因子,因为在返回递归值时该因子已经被解决了,但在第二种情况下,需要存储1,以便在解决递归值时进行评估。你可以尝试使用 njstrace(https://www.npmjs.com/package/njstrace)来检查执行情况。它的输出非常庞大,我将尽快留下准确的答案,现在只是将我的观察结果整理好。 - Vinod Louis
啊,你的意思是第二个需要评估表达式1并将其结果值保留在堆栈上,对吧。不确定优化器是否会消除它,尽管似乎njstrace会影响可能的优化。 - Bergi
是的,njstare会减少调用堆栈,但很明显可以看到对于第二种情况,堆栈比第一种情况更快地填满了。实际上,我猜这可以通过Java或C来很好地展示,同样的情况也会发生在那里。也许Java中的线程转储可以更好地解释这个问题。 - Vinod Louis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接