这个问题可能听起来相当简单,但这是我与另一位开发人员的争论。

我小心地在我可以的地方进行堆栈分配,而不是堆分配。他和我说话,看着我的肩膀,并评论说,这是没有必要的,因为他们的表现是一样的。

在我的印象中,增加堆栈是一个常数时间,而堆分配的性能取决于当前堆的复杂性,包括分配(找到合适大小的孔)和反分配(缩小孔以减少碎片,如果我没有弄错的话,许多标准库实现在删除过程中需要时间来做这件事)。

在我看来,这可能非常依赖于编译器。在这个项目中,我特别使用了一个用于PPC架构的Metrowerks编译器。对这种组合的深入了解将是最有帮助的,但一般来说,对于GCC和msvc++,情况如何?堆分配不如堆栈分配高效吗?没有区别吗?还是差异如此之小以至于变成了毫无意义的微观优化。


当前回答

堆栈的容量有限,而堆则不是。一个进程或线程的典型堆栈大约是8K。一旦分配,就不能更改大小。

堆栈变量遵循作用域规则,而堆变量则不遵循。如果你的指令指针超出了一个函数,所有与该函数相关的新变量都会消失。

最重要的是,您无法预先预测整个函数调用链。因此,仅200字节的分配就可能导致堆栈溢出。如果您正在编写一个库,而不是应用程序,这一点尤其重要。

其他回答

正如其他人所说,堆栈分配通常要快得多。

但是,如果复制对象的代价很高,那么如果不小心,在堆栈上分配可能会导致以后使用对象时的巨大性能损失。

例如,如果你在堆栈上分配了一些东西,然后将其放入容器中,那么在堆上分配并将指针存储在容器中会更好(例如使用std::shared_ptr<>)。同样的情况也适用于按值传递或返回对象,以及其他类似的情况。

重点是,尽管在许多情况下堆栈分配通常比堆分配更好,但有时如果你在不最适合计算模型的情况下费尽脑汁进行堆栈分配,它可能会导致比它解决的问题更多的问题。

堆栈的容量有限,而堆则不是。一个进程或线程的典型堆栈大约是8K。一旦分配,就不能更改大小。

堆栈变量遵循作用域规则,而堆变量则不遵循。如果你的指令指针超出了一个函数,所有与该函数相关的新变量都会消失。

最重要的是,您无法预先预测整个函数调用链。因此,仅200字节的分配就可能导致堆栈溢出。如果您正在编写一个库,而不是应用程序,这一点尤其重要。

class Foo {
public:
    Foo(int a) {

    }
}
int func() {
    int a1, a2;
    std::cin >> a1;
    std::cin >> a2;

    Foo f1(a1);
    __asm push a1;
    __asm lea ecx, [this];
    __asm call Foo::Foo(int);

    Foo* f2 = new Foo(a2);
    __asm push sizeof(Foo);
    __asm call operator new;//there's a lot instruction here(depends on system)
    __asm push a2;
    __asm call Foo::Foo(int);

    delete f2;
}

It would be like this in asm. When you're in func, the f1 and pointer f2 has been allocated on stack (automated storage). And by the way, Foo f1(a1) has no instruction effects on stack pointer (esp),It has been allocated, if func wants get the member f1, it's instruction is something like this: lea ecx [ebp+f1], call Foo::SomeFunc(). Another thing the stack allocate may make someone think the memory is something like FIFO, the FIFO just happened when you go into some function, if you are in the function and allocate something like int i = 0, there no push happened.

我想说的是,实际上GCC生成的代码(我还记得VS)不需要做堆栈分配的开销。

对以下函数表示:

  int f(int i)
  {
      if (i > 0)
      {   
          int array[1000];
      }   
  }

下面是生成的代码:

  __Z1fi:
  Leh_func_begin1:
      pushq   %rbp
  Ltmp0:
      movq    %rsp, %rbp
  Ltmp1:
      subq    $**3880**, %rsp <--- here we have the array allocated, even the if doesn't excited.
  Ltmp2:
      movl    %edi, -4(%rbp)
      movl    -8(%rbp), %eax
      addq    $3880, %rsp
      popq    %rbp
      ret 
  Leh_func_end1:

所以无论你有多少局部变量(甚至在if或switch内部),只有3880会改变为另一个值。除非你没有局部变量,否则这条指令只需要执行。所以分配局部变量没有开销。

除了与堆分配相比具有数量级的性能优势外,堆栈分配对于长时间运行的服务器应用程序更可取。即使是管理得最好的堆最终也会碎片化,导致应用程序性能下降。