Programming languages 为什么堆栈溢出仍然是一个问题?

Programming languages 为什么堆栈溢出仍然是一个问题?,programming-languages,stack,memory-management,Programming Languages,Stack,Memory Management,这个问题多年来一直困扰着我,考虑到这个网站的名字,这就是我要问的地方 为什么我们程序员仍然有这个StackOverflow问题 为什么在每种主要语言中,线程堆栈内存都必须在线程创建时静态分配 我将在C#/Java的上下文中发言,因为我使用它们最多,但这可能是一个更广泛的问题 固定的堆栈大小会导致巨大的问题: 除非绝对确定递归的深度很小,否则无法编写递归算法。递归算法的线性内存复杂性通常是不可接受的 没有廉价的方法来启动新线程。您必须为堆栈分配巨大的内存块,以考虑线程的所有可能用途 即使不使用非

这个问题多年来一直困扰着我,考虑到这个网站的名字,这就是我要问的地方

为什么我们程序员仍然有这个
StackOverflow
问题

为什么在每种主要语言中,线程堆栈内存都必须在线程创建时静态分配

我将在C#/Java的上下文中发言,因为我使用它们最多,但这可能是一个更广泛的问题

固定的堆栈大小会导致巨大的问题:

  • 除非绝对确定递归的深度很小,否则无法编写递归算法。递归算法的线性内存复杂性通常是不可接受的
  • 没有廉价的方法来启动新线程。您必须为堆栈分配巨大的内存块,以考虑线程的所有可能用途
  • 即使不使用非常深的递归,由于堆栈大小是一个任意的固定数字,您始终有耗尽堆栈空间的风险。考虑到StackOverflow通常是不可恢复的,在我看来这是一个大问题
现在,如果栈被动态调整大小,上面所有的问题都会得到很大的缓解,因为栈溢出只有在内存溢出时才可能发生

但事实并非如此。为什么?现代CPU是否存在一些基本的限制,使其不可能/效率低下?如果您考虑到重新分配会对性能造成的影响,这应该是可以接受的,因为人们总是使用像
ArrayList
这样的结构,而不会遭受太多的痛苦

所以,问题是,我是否遗漏了一些东西,堆栈溢出不是问题,或者我遗漏了一些东西,有很多语言使用动态堆栈,或者这是不可能实现/难以实现的重要原因

编辑: 有些人说,性能将是一个大问题,但请考虑如下:

  • 我们保持编译后的代码不变。堆栈访问保持不变,因此“通常情况”性能保持不变
  • 我们处理当代码试图访问未分配的内存并启动“重新分配”例程时发生的CPU异常。重新分配不会频繁,因为。应在大多数受保护模式的CPU上工作,而不会损失性能。没有
1)为了调整堆栈大小,您必须能够移动内存,这意味着堆栈大小调整后,指向堆栈上任何内容的指针都可能无效。是的,您可以使用另一个级别的间接寻址来解决此问题,但请记住,堆栈的使用非常频繁

2) 这明显使事情变得更复杂。栈上的Push/pop操作通常只需在CPU寄存器上执行一些指针运算即可。这就是为什么堆栈上的分配比免费存储上的分配快

3) 一些CPU(特别是微控制器)直接在硬件上实现堆栈,与主存储器分离

此外,如果您发现多余的堆栈空间是不必要的,您可以相应地设置堆栈大小

根据我的经验,堆栈溢出通常是由无限递归或递归函数引起的,这些递归函数在堆栈上分配巨大的数组,对于大多数案件来说,这似乎已经足够大了


固定堆栈机制对于大多数应用程序来说都能很好地工作,所以没有必要去改变它。如果没有,您可以随时推出自己的堆栈。

我个人从未遇到过不是由无限递归引起的堆栈溢出。在这些情况下,动态堆栈大小没有帮助,只是需要稍长的时间来耗尽内存。

任何会在典型的静态长度堆栈上导致堆栈溢出的代码都是错误的

  • 您可以将堆栈设置为类似std::vector的对象,但当它决定调整大小时,您的性能将极不可预测——而且无论如何,它很可能会一直这样做,直到所有堆都耗尽为止,这更令人恼火
  • 你可以把它做成一个std::list,它在O(1)处增长。然而,在静态堆栈上使用的指针算法对程序性能的各个方面都非常关键,因此它的速度会非常慢。语言被发明为具有一个返回值和任意数量的输入参数,因为这符合静态堆栈/指针算术范式
因此,一个动态调整大小的堆栈将是a)一个性能噩梦,B)无论如何都没有价值,因为您的堆栈不应该那么深。

堆栈是动态调整大小的,或者准确地说,是动态增长的。当堆栈无法进一步增长时,会出现溢出,这并不是说它耗尽了地址空间,而是说它增长到与用于其他目的的部分内存(例如,进程堆)冲突

也许你的意思是堆栈不能动态移动?其根源可能是堆栈与硬件紧密耦合。CPU拥有专用于线程堆栈管理的寄存器和逻辑堆(x86上的esp、ebp、call/return/enter/leave指令)。如果您的语言是编译的(甚至是jitted),那么您将绑定到硬件机制,无法移动堆栈

这种硬件“限制”可能会继续存在。在线程执行期间重新建立线程堆栈似乎远远不是硬件平台的合理要求(而且增加的复杂性将严重阻碍在这样一个假想的CPU上执行的所有代码,甚至是编译的)。你可以想象一个完全虚拟化的环境,其中没有这个限制,但是因为这样的代码不能进行JIT,所以速度会非常慢。你不可能用它做任何互动

为什么在每种主要语言中线程堆栈内存都必须是静态的
#include <deque>
#include <iostream>

size_t fac(size_t arg){
    std::deque<size_t> v;
    v.push_back(arg);
    while (v.back() > 2)
        v.push_back(v.back() - 1);
    size_t result = 1;
    for (size_t i = 0; i < v.size(); i++)
        result *= v[i];
    return result;
}

int main(int argc, char** argv){
    int arg = 12;
    std::cout << " fac of " << arg << " is " << fac(arg) << std::endl;
    return 0;
}