在堆上分配内存时,唯一的限制是可用RAM(或虚拟内存)。它使Gb的内存。
那么,为什么堆栈大小如此有限(大约1 Mb)呢?什么技术原因阻止您在堆栈上创建非常大的对象?
更新:我的意图可能不清楚,我不想在堆栈上分配大对象,也不需要更大的堆栈。这个问题仅仅是出于好奇。
在堆上分配内存时,唯一的限制是可用RAM(或虚拟内存)。它使Gb的内存。
那么,为什么堆栈大小如此有限(大约1 Mb)呢?什么技术原因阻止您在堆栈上创建非常大的对象?
更新:我的意图可能不清楚,我不想在堆栈上分配大对象,也不需要更大的堆栈。这个问题仅仅是出于好奇。
Answers:
我的直觉如下。堆栈不如堆容易管理。堆栈需要存储在连续的内存位置。这意味着您不能根据需要随机分配堆栈,但为此至少需要保留虚拟地址。保留的虚拟地址空间的大小越大,可以创建的线程就越少。
例如,一个32位应用程序通常具有2GB的虚拟地址空间。这意味着,如果堆栈大小为2MB(pthreads中的默认值),则最多可以创建1024个线程。对于Web服务器等应用程序来说,这可能很小。将堆栈大小增加到100MB(即,您保留100MB,但不一定立即将100MB分配给堆栈),将线程数限制为大约20,即使对于简单的GUI应用程序,这也可能会受到限制。
一个有趣的问题是,为什么我们在64位平台上仍然有此限制。我不知道答案,但我认为人们已经习惯了一些“堆栈最佳实践”:请小心在堆上分配大对象,并在需要时手动增加堆栈大小。因此,没有人发现在64位平台上添加“巨大”堆栈支持很有用。
尚未有人提及的一个方面:
有限的堆栈大小是一种错误检测和遏制机制。
通常,C和C ++中堆栈的主要工作是跟踪调用堆栈和局部变量,如果堆栈超出范围,则几乎总是在设计和/或应用程序行为中出错。
如果允许堆栈任意增大,这些错误(例如无限递归)将在很晚才被捕获,只有在操作系统资源耗尽之后。通过对堆栈大小设置任意限制可以防止这种情况。实际大小并不重要,除了要足够小以防止系统降级。
std::unique_ptr
用于编写析构函数(而不依赖于智能指针))。
这只是默认大小。如果您需要更多,则可以得到更多-通常是通过告诉链接器分配额外的堆栈空间。
大堆栈的不利之处在于,如果创建多个线程,则每个线程将需要一个堆栈。如果所有堆栈都在分配多MB,但不使用它,则会浪费空间。
您必须为您的程序找到适当的平衡。
有些人,例如@BJovke,认为虚拟内存基本上是免费的。的确,您不需要物理内存来支持所有虚拟内存。您必须至少能够将地址分配给虚拟内存。
但是,在典型的32位PC上,虚拟内存的大小与物理内存的大小相同-因为任何地址(无论虚拟与否)都只有32位。
由于进程中的所有线程共享相同的地址空间,因此必须在它们之间进行分配。在操作系统发挥作用之后,应用程序仅剩下2-3 GB。这个大小对于极限两者的物理和虚拟内存,因为那里只是没有任何其它地址。
一方面,堆栈是连续的,所以如果分配12MB,则当要低于所创建的内容时必须删除12MB。而且,移动物体变得更加困难。这是一个真实的示例,可能会使事情更容易理解:
假设您要在一个房间里堆放箱子。哪个更容易管理:
这两个例子是粗略的概括,在类比中有些地方公然是错误的,但它非常接近,希望可以帮助您看到这两种情况的优点。
以近到远的顺序来考虑堆栈。寄存器距离CPU较近(快速),堆栈距离较远(但仍相对较近),堆距离较远(访问缓慢)。
堆栈位于进程的堆上,但是仍然可以使用,因为它被连续使用,所以它可能永远不会离开CPU缓存,这使其比普通堆访问要快。这是保持堆栈合理大小的原因。使其尽可能快地缓存。分配大堆栈对象(可能会在溢出时自动调整堆栈大小)违反了这一原则。
因此,这是一种很好的性能范例,而不仅仅是过去的遗留问题。
我认为没有任何技术原因,但这将是一个奇怪的应用程序,它仅在堆栈上创建了一个巨大的超级对象。堆栈对象缺乏灵活性,灵活性会随着大小的增加而变得更加成问题-您不能在不破坏它们的情况下返回并且不能将它们排队到其他线程。
int main() { char buffer[1048576]; }
这是一个非常常见的新手问题。当然,有一个简单的解决方法,但是为什么我们必须解决堆栈大小问题?