为什么堆栈内存大小如此有限?

und*_*ndu 73 c++ memory-management

在堆上分配内存时,唯一的限制是可用RAM(或虚拟内存).它使Gb成为记忆.

那么为什么堆栈大小如此有限(约1 Mb)?什么技术原因阻止你在堆栈上创建真正的大对象?

更新:我的意图可能不明确,我不想在堆栈上分配大对象,我不需要更大的堆栈.这个问题只是纯粹的好奇心.

use*_*136 40

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

例如,32位应用程序通常具有2GB的虚拟地址空间.这意味着如果堆栈大小为2MB(在pthreads中为默认值),则可以创建最多1024个线程.对于Web服务器等应用程序而言,这可能很小.将堆栈大小增加到100MB(即,您保留100MB,但不一定要立即为堆栈分配100MB),会将线程数限制为大约20,这对于简单的GUI应用程序来说可能是有限的.

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

  • 这个答案大多是不正确的。堆栈在虚拟内存中是连续的,但不一定在物理内存中。并且堆栈的内存并不是立即物理分配的(特别是如果您有 2MB 堆栈)。它是在您开始使用时分配的 https://en.wikipedia.org/wiki/Demand_paging (3认同)
  • @ edA-qamort-ora-y:这个答案不是在谈论_allocation_,而是在谈论_virtual memory reserveration_,这几乎是免费的,而且肯定比mmap快得多. (2认同)

And*_*tin 26

尚未提及的一个方面:

有限的堆栈大小是错误检测和包含机制.

通常,C和C++中堆栈的主要工作是跟踪调用堆栈和局部变量,如果堆栈超出界限,则几乎总是设计和/或应用程序行为中的错误.

如果允许堆栈任意增大,那么只有在操作系统资源耗尽后才能很晚捕获这些错误(如无限递归).通过设置堆栈大小的任意限制来防止这种情况.实际尺寸并不重要,除了它足够小以防止系统退化.


Bo *_*son 14

它只是一个默认大小.如果您需要更多,您可以获得更多 - 通常是告诉链接器分配额外的堆栈空间.

拥有大型堆栈的缺点是,如果创建多个线程,则每个线程需要一个堆栈.如果所有堆栈都分配了多个MB,但没有使用它,则会浪费空间.

你必须找到适合你的程序的平衡.


像@BJovke这样的人认为虚拟内存基本上是免费的.确实,您不需要物理内存支持所有虚拟内存.您必须至少能够为虚拟内存分配地址.

但是,在典型的32位PC上,虚拟内存的大小与物理内存的大小相同 - 因为我们只有32位用于任何地址,虚拟或非虚拟.

因为进程中的所有线程共享相同的地址空间,所以它们必须在它们之间进行划分.在操作系统发挥作用之后,应用程序只剩下2-3 GB.这个大小对于极限两者的物理虚拟内存,因为那里只是没有任何其它地址.

  • @MartinJames:没有人说所有对象都应该在堆栈上,我们正在讨论为什么默认堆栈大小很小. (2认同)

小智 7

按照从近到远的顺序考虑堆栈。寄存器靠近CPU(快),栈有点远(但仍然相对接近),堆远离(慢访问)。

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

因此,它是性能的一个很好的范例,而不仅仅是旧时代的遗留物。

  • 虽然我确实相信缓存在人为减少堆栈大小的原因中起着重要作用,但我必须纠正“堆栈存在于堆上”的说法。堆栈和堆都存在于内存中(虚拟或物理)。 (4认同)

Sco*_*ain 5

一方面,堆栈是连续的,所以如果分配12MB,则当要低于所创建的内容时,必须删除12MB。而且,移动物体变得更加困难。这是一个真实的示例,可能会使事情更容易理解:

假设您在一个房间里堆放箱子。哪个更易于管理:

  • 将任何重量的箱子互相叠放,但是当您需要在底部放东西时,则必须撤消整个堆垛。如果要从堆中取出物品并交给其他人,则必须拿走所有箱子并将箱子移到另一个人的堆上(仅限堆放)
  • 您将所有箱子(除了很小的箱子)放在一个特殊的区域,不要在其他区域上堆放东西,而在将其放在纸上(指针)写下来的地方写下来堆。如果您需要将盒子交给其他人,您只需将纸堆上的纸条交给他们,或者给他们纸的复印件,然后将原件放在堆中。(堆栈+堆)

这两个例子是粗略的概括,在类比中有些地方公然是错误的,但它非常接近,希望可以帮助您看到这两种情况的优点。

  • 虽然我的评论是由于误解(我删除了它),但我仍然不同意这个答案。从堆栈顶部移除 12MB 实际上是一个操作码。它基本上是免费的。编译器也可以并且确实欺骗了“堆栈”规则,所以不,他们不必在展开返回对象之前复制/移动对象。所以我认为你的评论也是不正确的。 (2认同)