当从堆栈转到堆时,合理的指示性大小是多少?

12
WCHAR bitmapPathBuffer[512]

堆栈分配是否可行?还是对于这个大小最好使用堆内存?何时更好地从堆栈转换为堆的合理指示大小是多少......所有人都说"这取决于",但我们的大脑需要一些限制或方向。


可能是在C++中的堆栈、静态和堆的重复问题。 - moooeeeep
4
不是关于何时使用栈或堆,而是关于合理限制。 - Brans Ds
使用 ulimit -a 命令获取您的限制,如果您正在使用 Linux。 - mch
1
正如雷蒙德·陈经常说的那样,如果你不得不问,那么你可能正在做错什么。 - Skizz
没有通用的一刀切合理限制。就目前而言,这个问题无法得到普遍回答,因为它完全取决于您应用程序的目标平台。您可能需要提供一些上下文。 - moooeeeep
这个函数是递归的吗?它是在一个非常深的链的基础上,还是在尾部、中间,同时包含一半k块?什么样的数据适合512字节,固定缓冲区通常是附近错误的标志。 - Yakk - Adam Nevraumont
5个回答

7
您可能需要检查系统的默认堆栈大小,并考虑应用程序对递归的使用,以确定一些合理的阈值。
无论如何,对于典型的台式电脑,我认为将不会被递归调用且没有任何异常情况的函数放在堆栈上的大小约为100kb是合理的(在看到Windows的限制后,我必须将其修正为更小)。您可能能够在特定系统上增加或减少一个数量级,但大约在这个点上,您会开始关心检查系统限制。
如果您发现在许多函数中都在使用堆栈,最好仔细考虑这些函数是否可以相互调用,或者只需动态分配内存(最好隐式地通过使用vector、string等)并不要担心它。
100kb指南基于从网络上获取的这些默认堆栈大小数字
platform    default size    # bits  # digits
    ===============================================================
SunOS/Solaris   8172K bytes <=39875 <=12003 (Shared Version)
Linux       8172K bytes <=62407 <=18786
Windows     1024K bytes <=10581 <=3185  (Release Version)
cygwin      2048K bytes <=3630  <=1092

Solaris/Linux和Windows之间存在很大的差别。在Solaris或Linux上,最大堆栈大小不是在链接时确定,而是在运行时确定;根据上下文,很容易将您的程序包装在调用ulimit -s的脚本中。 - James Kanze
正如James所说,在某些系统上,这些限制可以增加或减少,但通常如果你是为该环境编写代码,并且假设默认大小的话,如果有人将它们大幅缩小,他们可能希望你的应用程序在内存使用降低到那个水平时终止运行。 - Tony Delroy
我的意思是,如果你是为那个环境编写代码,你可以将它们放大,或者在用户手册中要求用户这样做。 - James Kanze

4
正如其他人所说,这个问题的答案取决于你运行的系统。为了得出一个明智的答案,你需要知道以下几点:
  • 默认堆栈大小。对于除主线程以外的线程,或者如果你使用闭包或第三方线程或协程库,可能会有所不同。
  • 系统堆栈是否动态调整大小。在某些系统上,堆栈可以自动增长到一定程度
  • 在一些平台上(例如基于ARM或PowerPC的系统),存在“红区”。如果你在一个叶子函数中(即不调用其他函数的函数),如果你的堆栈使用量小于红区的大小,编译器可以生成更高效的代码。

作为一般规则,我同意其他回答者在桌面系统上,16-64KB左右是合理的限制,但这还取决于递归深度等因素。肯定的是,大型堆栈帧是代码异味,应该进行调查以确保它们是必要的。

特别是,考虑任何分配在堆栈上的缓冲区的长度...它们是否真的足够大来容纳任何可能的输入?你是否在运行时检查避免溢出?例如,在你的例子中,你是否确信 bitmapPathBuffer 的长度永远不会超过512个WCHAR?如果你不确定最大长度一定,那么堆可能更好。即使如此,在对抗性环境中,你可能要对其设置一个较大的上限,以避免涉及内存耗尽的攻击。


1
你最后一段暗示了另一个动态分配数组的重要原因:缓冲区溢出的后果。如果你使用std::vector进行动态分配,并使用正确的选项进行编译,缓冲区溢出将导致程序崩溃并显示可识别的错误。如果你使用本地分配,缓冲区溢出可能会改变函数的返回地址,从而导致执行不良代码。 - James Kanze
1
有一件事需要提到,那就是递归:堆栈大小限制是针对整个堆栈的,将所有当前运行的函数加起来。因此,如果您正在实现某种递归算法,大型堆栈分配会变得更加危险,而且速度更快。 - uliwitness

2

答案是“这要看情况”。

如果您定义了许多这样的变量,或者在您的函数及其调用的函数中进行相对较大的堆栈分配,则可能会发生堆栈溢出。

Win32可执行文件的典型默认堆栈大小为1MB。如果您分配的内存超过此限制,那么您可能会遇到问题,应该将最大的分配更改为堆上的分配。

我会遵循一个简单的规则-如果您的分配超过16-64KB,那么请在堆上分配。否则,在堆栈上分配应该是可以的。


2
现代编译器在正常情况下使用大约1兆字节的堆栈大小。因此,1千字节对于简单程序来说不是问题。
如果程序非常复杂,调用链中的其他函数也使用堆栈的大部分空间,当前函数深度很深等,则最好避免使用大型自动变量。
如果您使用递归,则应仔细考虑其深度。
如果您编写的函数将在其他项目或其他人使用,则永远不知道它是否会在递归函数或堆栈深处被调用。因此,在这种情况下通常最好避免使用大型自动变量。

2

没有硬性限制,但您可能需要考虑分配失败的情况。如果本地变量的分配失败,则程序会崩溃;如果动态变量的分配失败,则会收到(或应该收到)异常。因此,我倾向于对大约1K以上的任何内容使用动态分配(以std::vector的形式)。另外,std::vector在编译时没有优化时进行边界检查(至少是我使用的实现),这也是一个优点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接