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

时间:2012-05-07 13:29:48

标签: c++ memory-management

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

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

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

8 个答案:

答案 0 :(得分:40)

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

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

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

答案 1 :(得分:25)

尚未提及的一个方面:

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

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

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

答案 2 :(得分:14)

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

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

您必须为您的计划找到适当的余额。


有些人,比如@BJovke,相信虚拟内存基本上是免费的。确实,您不需要物理内存支持所有虚拟内存。您必须至少能够为虚拟内存提供地址。

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

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

答案 3 :(得分:5)

首先,堆栈是连续的,所以如果你分配12MB,当你想要低于你创建的任何东西时,你必须删除12MB。移动物体也变得更加困难。这是一个真实世界的例子,可以让事情更容易理解:

假设您在房间周围堆放盒子。哪个更容易管理:

  • 将任何重量的盒子堆叠在一起,但是当你需要在底部拿东西时,你必须撤消整个堆。如果你想从一堆物品中取出物品给别人,你必须取下所有箱子并将箱子移到另一个人的堆里(仅限堆叠)
  • 你把所有的盒子(除了真正的小盒子)都放在一个特殊的区域,在那里你不要把东西堆叠在其他东西之上并写下你把它放在一张纸上的位置(一个指针)并把它放在一起桩上的纸。如果您需要将盒子交给别人,您只需将它们从纸堆中取出来,或者只是给它们一张纸的复印件,并将原件留在堆中。 (堆栈+堆)

这两个例子是粗略概括,并且在类比中有一些明显错误的点,但它足够接近,希望能帮助你看到两种情况下的优势。

答案 4 :(得分:1)

在一个 100MB 的堆栈中分配大对象会使大多数机器无法将它们一次加载到缓存中,这几乎违背了堆栈的目的。

堆栈的重点是将属于同一范围的小对象(因此通常需要一起或彼此靠近)一起存储在连续的内存地址中,以便程序可以将它们全部加载同时进入缓存,最大限度地减少缓存未命中,一般来说,CPU 必须等待的时间,直到它从较慢的 RAM 中获取一些丢失的数据。

存储在堆栈中的 50MB 对象无法放入缓存中,这意味着在每个缓存行之后都有一个 CPU 等待时间,直到从 RAM 中取出下一块数据,这意味着会阻塞调用堆栈而不是与从堆加载相比,获得任何显着的好处(在速度方面)。

答案 5 :(得分:0)

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

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

所以这是一个很好的表现范例,而不仅仅是过去的遗留。

答案 6 :(得分:0)

您认为需要大量筹码的许多事情都可以通过其他方式完成。

Sedgewick的“算法”有一些很好的例子,可以通过迭代替换递归来从递归算法(如QuickSort)中“移除”递归。实际上,算法仍然是递归的,并且仍然是堆栈,但是您在堆上分配排序堆栈,而不是使用运行时堆栈。

(我赞成第二版,使用Pascal中给出的算法。它可以用于8美元。)

另一种看待它的方法是,如果你认为你需要一个大堆栈,你的代码就会效率低下。有一种更好的方法可以使用更少的堆栈。

答案 7 :(得分:-8)

我认为没有任何技术原因,但它会是一个奇怪的应用程序,只是在堆栈上创建了一个巨大的超级对象。堆栈对象缺乏灵活性,随着大小的增加会变得更加棘手 - 如果不破坏它们就无法返回,并且无法将它们排队到其他线程。