C++如何在堆栈上动态分配内存?

52

有没有办法在堆栈上分配内存,而不是在堆上?我找不到一本好的书来解决这个问题,这里有人有想法吗?


1
@Neil:如何快速分配/释放临时工作空间? - André Caron
2
@Neil:如果函数需要一些小而又可变的临时对象呢?你可以使用一个vector,但如果该函数在紧密循环中被调用,那么如果内存可以快速分配和释放而不必担心碎片化,那就太棒了。 - André Caron
1
@Andre - 然后在堆栈上分配一小部分固定数量的对象。如果我们不知道上限,alloca 无论如何都会出问题。 - Bo Persson
1
@Alf 我从2.0开始就一直在为Windows编程,但从未使用过它,也没有见过别人使用过。 - user2100815
@Neil:我猜所有这些调用都隐藏在Win32API的ANSI版本中,因为内核只处理Unicode版本。 - André Caron
显示剩余12条评论
7个回答

62

使用alloca()(有时称为_alloca()_malloca()),但要非常小心——它会在离开函数而不是超出作用域时释放其内存,因此如果在循环中使用它,您很快就会耗尽内存。

例如,如果你有一个函数:

int foo( int nDataSize, int iterations ) 
{
   for ( int i = 0; i < iterations ; ++i )
   {
      char *bytes = alloca( nDataSize );
      // the memory above IS NOT FREED when we pass the brace below!
   } 
   return 0;
}  // alloca() memory only gets freed here

每次循环时,alloca()都将额外分配nDataSize字节。只有在函数返回后,这些alloca()字节才会被释放。因此,如果您的nDataSize为1024,iterations为8,则在返回之前会分配8千字节。如果nDataSize=65536且iterations=32768,则总共分配65536×32768=2,147,483,648个字节,几乎肯定会使堆栈溢出并导致崩溃。

趣闻轶事: 如果您超出缓冲区的末尾,尤其是如果您将缓冲区传递给另一个函数并且该子函数错误地估计了缓冲区的长度,那么您很容易遇到麻烦。 我曾经修复过一个非常有趣的错误,我们使用alloca()为渲染TrueType字体字形创建临时存储,然后将其发送到GPU内存。 我们的字体库在计算字形大小时没有考虑到瑞典语中Å字符的变音符号,因此它告诉我们要分配n字节来存储字形,然后实际上渲染了n+128字节。额外的128个字节写入调用堆栈,覆盖返回地址并导致非常痛苦的非确定性崩溃!


17
我们在嵌入式开发中实际上不使用标准C++的其中一个原因。 =P - Crashworks
1
如果我在递归函数中使用它会怎样?会有任何副作用吗?另外,我不太理解最后一部分,“如果你在循环中使用它会崩溃”。但是循环仍然在函数内部,对吧? - Mark
@Dani 如果这个函数是void类型的呢? - Mark
5
@Mark:好的,停下来想一想。alloca()函数在做什么,函数的返回类型又是如何影响它的呢? - Crashworks
3
链接已损坏,我想阅读关于你的错误的内容! - lmat - Reinstate Monica
显示剩余3条评论

9

由于这是标记为C++,通常只需在正确的作用域中声明所需的对象。它们分配在堆栈上,并保证在作用域退出时被释放。这是C++相对于C的关键优势之一,称为RAII。不需要mallocnew,尤其是不需要alloca


2
问题在于许多C++对象会为自己分配内存。 - Zan Lynx
@Zan Lynx - 当然没错。但在什么情况下,您会在堆栈上分配这样的对象图呢? - Steve Townsend
10
您可以调用一个填充向量的函数。您可能需要一个字符串。如果您需要这些东西非常快和线程安全,那么栈存储正是正确的地方。如果您不需要这些东西超出函数的生命周期,那么栈存储就是完美的选择。 - Zan Lynx
@Zan - 对于专业应用程序,我可以理解这一点。我认为如果OP澄清了为什么有这种感知需求,那么说服我会更容易。在大多数情况下,我的观点是不需要的。 - Steve Townsend
@Steve - 我主要在嵌入式环境中工作,经常会受到堆分配内存的严格限制。 - Greg
@Greg - 已经注意到了。如果OP有这样的限制,那么对他们来说可能会更加复杂。 - Steve Townsend

3

这是非标准的C++,需要注意。 - Sebastian

3

您可以声明一个本地的char[1024]或任何您想要的字节数(在一定程度上),然后获取该本地地址,作为指向堆栈上该内存块的指针。虽然不是完全动态的,但如果需要,您可以将此内存与自己的内存管理器包装起来。


1
如果不是最佳答案,那么这应该是针对该特定问题的第二好答案。它干净简洁,不像_alloca答案。 - MarkoPaulo
6
简洁明了,但是错误,因为问题特别询问的是动态(而非静态)堆栈分配。 - jeremyong

3

讨论动态内存分配的文章

We can allocate variable length space dynamically on stack memory by using function _alloca. This function allocates memory from the program stack. It simply takes number of bytes to be allocated and return void* to the allocated space just as malloc call. This allocated memory will be freed automatically on function exit.

So it need not to be freed explicitly. One has to keep in mind about allocation size here, as stack overflow exception may occur. Stack overflow exception handling can be used for such calls. In case of stack overflow exception one can use _resetstkoflw() to restore it back.

So our new code with _alloca would be :

int NewFunctionA()
{
   char* pszLineBuffer = (char*) _alloca(1024*sizeof(char));
    …..
  // Program logic
     ….
  //no need to free szLineBuffer
  return 1;
}

2
欢迎来到SO!请考虑在您的答案中引用最重要的部分,因为链接可能会失效。 - Mateng
你好,欢迎来到SO!通常情况下,仅指向另一个答案的回答不被视为答案。请参见http://meta.stackexchange.com/a/118694以获得澄清;-) - Don Question
虽然alloca是一种实现它的方法,但有很多缺点使它成为一种不好的实践。请参考这里的讨论; https://dev59.com/rnNA5IYBdhLWcg3wUL1Y#1018865 - ϹοδεMεδιϲ

0

如果 C++ 允许使用(非静态)const 值作为数组边界,那将更加容易。

目前,我知道的最佳方法是通过递归实现。有各种巧妙的技巧可供选择,但我知道的最简单的方法是让您的程序声明一个固定大小的数组,并填充和操作它所拥有的内容。当完成时,如果需要更多空间来完成任务,则调用自身。


2
你回答了什么问题? - Daniel
@André Caron - 没错。我也不会称之为“显而易见”。Ada允许您毫不犹豫地执行此操作。也许在C++中允许相同的操作会有害,但从我的角度来看,这似乎是语言的任意限制。在改变之前,我知道唯一标准的解决方法是将问题模块化并使用递归。 - T.E.D.
@T.E.D. 这不会改变 - 标准委员会和 C++ 社区强烈反对它,我必须说很多 C 用户也反对。 - user2100815
@T.E.D - 标准化alloca是不可能的,除非要求所有实现都必须有一个堆栈。如果不能保证基于堆栈,那么它就毫无用处,我们都可以使用std::vector代替。 - Bo Persson
@Bo 我不确定那个。C99已经成功地将变量数组分配在堆栈上进行了标准化。问题不在于你不能将其标准化,而在于这是一个不好的想法。 - user2100815
显示剩余10条评论

0
你可以使用BDE C++库,例如。
const int BUFFER_SIZE = 1024;
char      buffer[BUFFER_SIZE];

bdlma::BufferedSequentialAllocator allocator(buffer, BUFFER_SIZE);
bsl::vector<int>                   dataVector(&allocator);

dataVector.resize(50);

BDE 提供了全面的分配器选项,以及像 bsl::vector 这样的集合,可以使用多态分配器而不改变容器类型。
你还可以考虑:

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