暴力算法会导致堆栈溢出吗? (递归)

Could a brute-force algorithm cause a stack overflow? (recursion)

假设我们有一个递归暴力函数。
(我特别想知道暴力函数,因为它们可以轻松地递归调用自己一百万次。)
像这样,例如:

function BruteForce(chars, min, max, prefix, stage) {
    for (var i = 0, len = chars.length; i < len; i++) {
        if (stage >= min-1)
            console.log(prefix + chars[i])
        if (stage < max-1)
            BruteForce(chars, min, max, prefix + chars[i], stage+1)
   }
}

BruteForce("abc", 1, 5, "", 0)

(现在考虑这个伪代码,因为我的问题不仅仅是 JavaScript。)

这样的函数不会用新参数填充堆栈直到溢出吗?

如果你在 C/C++ 等中执行类似的东西会发生什么?
调用约定重要吗?不同的调用约定能够很好地处理这个问题吗?

为什么上面的 JavaScript 代码不会导致堆栈溢出?

What would happen if you execute something like that in C/C++ and the like? 你只会得到一个堆栈溢出。终端会输出与之相关的错误信息。注意程序会正确执行到一个点,然后会抛出栈溢出错误,执行会突然终止。

Why doesn't the Javascript code cause a stack overflow? 我以前没有使用 javascript 的经验,但原因似乎很简单,就是堆栈还没有溢出。如果你有足够深的递归,堆栈应该会溢出。为了测试这一点,为什么不将 start 初始化为 0 并将 max 设置为 10^9。递归可能不会导致堆栈溢出的一种情况是,如果语言不使用堆栈进行函数递归。

JavaScript 代码不会导致堆栈溢出。作为一种垃圾收集语言,一切都放在堆上。现在有可能最终堆会碰到堆栈,并导致问题。但是现在堆可能大到可能永远不会发生,不像堆栈总是有限的。

让我们试着一一回答您的问题:

Wouldn't a function like that fill the stack with new arguments until it overflows?

在某些情况下是的。这取决于参数本身以及堆栈的处理方式,这非常依赖于实现。

What would happen if you'd execute something like that in C/C++ and the like?

一般来说,C/C++ 提供的功能不如 "stack handling"。堆栈溢出是未定义的行为。

在嵌入式系统上,您基本上会为堆栈分配一些内存,如果溢出了……好吧,您会继续覆盖内存的其他部分(堆、静态数据……)。最终,你会 运行 可用内存不足,然后会发生什么取决于 CPU 和你的 运行 时间 - 崩溃、失速或类似的事情。

在托管系统(Windows、POSIX...)上,堆栈溢出可能会涉及页面错误异常,这可能会提示运行分配新页面的时间如果可能,将它们添加到堆栈中。但是,只有当您的 运行time 像那样工作时,它才可能像嵌入式系统一样运行,或者根本不处理页面错误(因此您的程序会崩溃)。

Does the calling convention matter? How well would the different calling conventions be able to handle this?

不多,因为这几乎必须使用将参数放在堆栈上的调用约定。哪个在前,哪个在后并不重要(您也不会在函数名称前加下划线)。 "register" 调用约定("fast call" 或您的编译器调用它的任何内容)将被递归函数忽略,或者只是将参数放入堆栈,然后复制到寄存器,然后函数将启动.

即使在滑动寄存器 window CPUs(如 SPARC)上,也没关系,因为寄存器 window 大部分都很浅,你会再次回到使用堆栈。

And why doesn't the JavaScript code above cause a stack overflow or so?

AFAICT,上面的代码没有很深的堆栈 "nesting"(你 "nest" 高达 max 而它是 5),并且由于Javascript 中的几乎所有内容都是 int、double 或指针(来自低级内存 POV),您的五个参数不会占用太多内存(可能约为 40 字节)。现在,如果您要将 max 增加到 2^31 之类的值,我猜可能会有麻烦。 :)

此外,请记住,Javascript 解释器可能会做一些奇怪的事情,比如弄清楚你永远不会改变 charsminmax 并且只是不' 每次都将它们放在堆栈上(从而节省 space)。理论上,甚至 C/C++ 编译器也可以这样做,但只是作为某些 "Whole program optimization".

的一部分