Why is stack memory size so limited?

When you allocate memory on the heap, the only limit is free RAM (or virtual memory). It makes Gb of memory.

So why is stack size so limited (around 1 Mb)? What technical reason prevents you to create really big objects on the stack?

Update: My intent might not be clear, I do not want to allocate huge objects on the stack and I do not need a bigger stack. This question is just pure curiosity!

39717 次浏览

首先,堆栈是连续的,所以如果您分配12 MB,那么当您想要低于所创建的内容时,必须移除12 MB。移动物体也变得更加困难。下面是一个真实世界的例子,可能会让事情更容易理解:

假设你正在一个房间周围堆放箱子,这样比较容易管理:

  • 把任何重量的盒子叠在一起,但是当你需要把东西放在底部的时候,你必须把整个盒子都拆开。如果你想把一个物品从堆里拿出来给别人,你必须把所有的盒子都拿下来,然后把盒子移到别人的堆里(仅限堆栈)
  • 你把所有的盒子(除了非常小的盒子)放在一个特殊的地方,在那里你不会把东西堆在其他东西上面,而是把它写在一张纸上(一个指针) ,然后把纸放在那一堆上。如果你需要把盒子交给别人,你只需要把纸条从你的文件堆里递给他们,或者只是给他们一份文件的复印件,然后把原件放在你的文件堆里。(堆栈 + 堆)

这两个例子都是粗略的概括,在类比中有些地方明显是错误的,但是它们足够接近,希望能够帮助您看到这两种情况的优点。

我的直觉是。堆栈不像堆那样容易管理。堆栈需要存储在连续的内存位置。这意味着您不能根据需要随机分配堆栈,但是您至少需要为此保留虚拟地址。保留的虚拟地址空间的大小越大,可以创建的线程就越少。

For example, a 32-bit application generally has a virtual address space of 2GB. This means that if the stack size is 2MB (as default in pthreads), then you can create a maximum of 1024 threads. This can be small for applications such as web servers. Increasing the stack size to, say, 100MB (i.e., you reserve 100MB, but do not necessarily allocated 100MB to the stack immediately), would limit the number of threads to about 20, which can be limiting even for simple GUI applications.

一个有趣的问题是,为什么我们在64位平台上仍然有这个限制。我不知道答案,但我假设人们已经习惯了一些“堆栈最佳实践”: 小心地在堆上分配巨大的对象,如果需要,手动增加堆栈大小。因此,没有人认为在64位平台上添加“巨大的”堆栈支持是有用的。

它只是默认大小。如果需要更多,可以得到更多——通常是通过告诉链接器分配额外的堆栈空间。

拥有大堆栈的缺点是,如果您创建了许多线程,它们将需要每个堆栈一个。如果所有的栈都分配了多 MBs,但是没有使用它,那么空间将被浪费。

你必须为你的程序找到适当的平衡。


Some people, like @BJovke, believe that virtual memory is essentially free. It is true that you don't need to have physical memory backing all the virtual memory. You do have to be able to at least give out addresses to the virtual memory.

然而,在一台典型的32位 PC 上,虚拟内存的大小与物理内存的大小相同——因为我们只有32位的任何地址,虚拟或非虚拟。

因为进程中的所有线程共享相同的地址空间,所以必须在它们之间进行划分。在操作系统完成它的部分之后,应用程序“只剩下”2-3 GB 的空间。这个大小是物理 都有和虚拟内存的限制,因为没有更多的地址了。

我不认为有任何技术原因,但这将是一个奇怪的应用程序,只是创建了一个巨大的超级对象堆栈。堆栈对象缺乏灵活性,随着大小的增加,这种灵活性变得更加成问题——您不能在不销毁它们的情况下返回,也不能将它们排队到其他线程。

考虑一下堆栈的顺序,从近到远。寄存器接近 CPU (快速) ,堆栈稍远一些(但仍然相对接近) ,堆离得很远(访问速度慢)。

堆栈当然存在于堆中,但是由于它是连续使用的,所以它可能永远不会离开 CPU 缓存,这使得它比普通的堆访问更快。 这就是保持堆栈大小合理的原因; 以尽可能多地保持其缓存。分配大堆栈对象(可能会在出现溢出时自动调整堆栈大小)违背了这一原则。

因此,这是一个很好的表现范例,而不仅仅是从旧时代遗留下来的。

还有一个方面没有人提到:

有限的堆栈大小是一种错误检测和包容机制。

一般来说,C 和 C + + 中堆栈的主要工作是跟踪调用堆栈和本地变量,如果堆栈超出了界限,几乎总是在设计和/或应用程序的行为中出错。

如果堆栈被允许任意增大,那么只有在操作系统资源耗尽之后,这些错误(比如无限递归)才会被发现得非常晚。可以通过设置堆栈大小的任意限制来防止这种情况。实际的大小并不那么重要,除了它足够小以防止系统退化之外。

许多你认为你需要一个大堆栈的事情,可以通过其他方式来完成。

Sedgewick's "Algorithms" has a couple good examples of "removing" recursion from recursive algorithms such as QuickSort, by replacing the recursion with iteration. In reality, the algorithm is still recursive, and there is still as stack, but you allocate the sorting stack on the heap, rather than using the runtime stack.

(I favor the second edition, with algorithms given in Pascal. It can be had used for eight bucks.)

另一种看待它的方式是,如果您认为需要一个大堆栈,那么您的代码是低效的。有一个更好的方法,使用较少的堆栈。

在一个100MB 的堆栈中分配大型对象将使得大多数机器不可能将它们立即加载到缓存中,这几乎违背了堆栈的目的。

堆栈的关键在于将属于相同作用域的小对象(因此,通常需要一起存储或者彼此接近)存储在连续的内存地址中,这样程序就可以同时将它们全部加载到缓存中,从而最小化缓存丢失,而且一般来说,CPU 必须等待的时间,直到它从较慢的 RAM 中获得一些丢失的数据。

一个存储在堆栈中的50MB 对象不能放入缓存中,这意味着在每一条缓存线路之后都会有一个 CPU 等待时间,直到下一块数据从 RAM 中带出,这意味着一个将会阻塞调用堆栈,并且与从堆中加载相比没有任何显著的好处(就速度而言)。

如果您可以有一个无限的堆栈,那么每个虚拟地址都可能被堆栈使用。如果堆栈可以使用每个地址,那么堆就无处可去。您为堆变量选择的每个地址都可能被不断增长的堆栈覆盖。

换句话说,堆栈上的变量和堆上的变量占用相同的虚拟地址空间。我们需要一些方法来防止堆分配器将数据分配到堆栈可能增长到的位置。堆栈大小是一种简单的方法。堆分配器知道堆栈地址已被获取,因此它使用了其他内容。