关于内存管理:C ++中正确的堆栈和堆使用情况?

Proper stack and heap usage in C++?

我已经编程了一段时间,但主要是Java和C语言。事实上,我从来没有独自管理过记忆。我最近开始在C++编程,我有点困惑什么时候我应该存储在堆栈上的东西,什么时候存储在堆上。

我的理解是,频繁访问的变量应该存储在堆栈和对象上,很少使用的变量和大型数据结构都应该存储在堆上。这是正确的还是我不正确?


不,堆栈和堆之间的区别不是性能。它的生命周期:函数中的任何局部变量(任何不malloc()或新的变量)都存在于堆栈中。当您从函数返回时,它就会消失。如果您希望某个东西的寿命比声明它的函数长,则必须在堆上分配它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Thingy;

Thingy* foo( )
{
  int a; // this int lives on the stack
  Thingy B; // this thingy lives on the stack and will be deleted when we return from foo
  Thingy *pointerToB = &B; // this points to an address on the stack
  Thingy *pointerToC = new Thingy(); // this makes a Thingy on the heap.
                                     // pointerToC contains its address.

  // this is safe: C lives on the heap and outlives foo().
  // Whoever you pass this to must remember to delete it!
  return pointerToC;

  // this is NOT SAFE: B lives on the stack and will be deleted when foo() returns.
  // whoever uses this returned pointer will probably cause a crash!
  return pointerToB;
}

为了更清楚地理解堆栈是什么,请从另一端来理解它——而不是试图从高级语言的角度来理解堆栈的功能,请查看"调用堆栈"和"调用约定",并查看调用函数时机器的真正功能。计算机内存只是一系列地址;"堆"和"堆栈"是编译器的发明。


我会说:

如果可以的话,将它存储在堆栈上。

如果需要,将它存储在堆中。

因此,首选堆栈而不是堆。无法在堆栈上存储某些内容的一些可能原因是:

  • 它太大了——在32位操作系统上的多线程程序上,堆栈的大小很小而且固定(至少在线程创建时)(通常只有几个兆)。这样就可以在不耗尽地址空间的情况下创建大量线程。对于64位程序或单线程(无论如何是Linux)程序,这不是主要问题。在32位Linux下,单线程程序通常使用动态堆栈,动态堆栈可以一直增长,直到到达堆的顶部。
  • 您需要在原始堆栈帧的范围之外访问它——这确实是主要原因。

使用合理的编译器,可以在堆上分配非固定大小的对象(通常是编译时大小未知的数组)。


这比其他答案暗示的更微妙。根据您如何声明堆栈上的数据和堆栈上的数据之间没有绝对的界限。例如:

1
std::vector<int> v(10);

在函数体中,声明堆栈上十个整数的vector(动态数组)。但是由vector管理的存储不在堆栈上。

啊,但是(其他答案表明)存储的生存期是受vector本身的生存期的限制的,这里是基于堆栈的,所以它的实现方式没有什么不同——我们只能将它作为具有值语义的基于堆栈的对象来处理。

不是这样。假设函数是:

1
2
3
4
5
6
7
8
void GetSomeNumbers(std::vector<int> &result)
{
    std::vector<int> v(10);

    // fill v with numbers

    result.swap(v);
}

因此,任何具有swap函数(以及任何复杂值类型都应该有一个)的对象都可以作为对某些堆数据的可重新绑定引用,在保证该数据的单个所有者的系统下。

因此,现代C++方法是决不将堆数据的地址存储在裸本地指针变量中。所有堆分配都必须隐藏在类中。

如果这样做,您可以将程序中的所有变量视为简单的值类型,并完全忽略堆(除了为一些堆数据编写一个新的值(如包装类),这应该是不寻常的)。

您只需要保留一点特殊的知识来帮助您优化:在可能的情况下,而不是像这样将一个变量分配给另一个变量:

1
a = b;

像这样交换它们:

1
a.swap(b);

因为它更快,而且不会抛出异常。唯一的要求是你不需要b继续持有相同的价值(它将得到a的价值,而这将在a = b中被破坏)。

缺点是这种方法强制您通过输出参数从函数返回值,而不是实际返回值。但是他们在C++ 0x中用rValk引用来修复。

在最复杂的情况下,您将把这个想法推向极端,并使用一个智能指针类,比如已经在tr1中的shared_ptr。(尽管我认为如果你需要它,你可能已经超出了标准C++的适用范围。)


如果需要在创建该项的函数的作用域之外使用该项,您还可以将该项存储在堆中。与堆栈对象一起使用的一个习语叫做raii——这涉及到使用基于堆栈的对象作为资源的包装器,当对象被破坏时,资源将被清除。基于堆栈的对象更容易跟踪何时抛出异常——您不需要担心在异常处理程序中删除基于堆栈的对象。这就是为什么在现代C++中通常不使用原始指针的原因,你会使用一个智能指针,它可以是一个基于堆栈的包装器,用于一个基于堆的对象的原始指针。


另外一个答案是,它也可以是关于性能的,至少有一点。不是说你应该担心这个,除非它与你有关,而是:

在堆中分配需要找到一个跟踪内存块,这不是一个固定时间操作(需要一些周期和开销)。随着内存碎片化和/或接近100%的地址空间,这可能会变慢。另一方面,堆栈分配是恒定的时间,基本上是"自由"操作。

要考虑的另一件事(同样,只有当它成为问题时才真正重要)是,通常堆栈大小是固定的,并且可以比堆大小低很多。因此,如果您正在分配大对象或许多小对象,那么您可能希望使用堆;如果堆栈空间用完,则运行时将抛出站点标题异常。通常不是什么大不了的事,而是另一件需要考虑的事情。


堆栈更高效,更容易管理范围内的数据。

但是堆应该用于比KB更大的任何东西(在C++中很简单,只需在堆栈上创建一个EDCOX1×8)以保存指向分配内存的指针。

考虑一个不断调用自身的递归算法。很难限制和或猜测堆栈的总使用量!而在堆上,分配器(malloc()new可以通过返回NULLthrow来指示内存不足。

来源:Linux内核,其堆栈不大于8KB!


为了完整起见,您可以阅读MiroSamek的文章,讨论在嵌入式软件环境中使用堆的问题。

一堆问题


根据变量的分配方式,可以选择是在堆上还是在堆栈上进行分配。如果使用"new"调用动态分配某些内容,则是从堆中分配。如果将某个对象分配为全局变量或函数中的参数,则该对象将在堆栈上分配。


也许这个问题已经得到了很好的回答。我想向您指出下面的一系列文章,以加深对低级细节的理解。亚历克斯·达比有一系列文章,他会带你通过一个调试器。下面是第3部分关于堆栈的内容。http://www.altdevblogaday.com/2011/12/14/c-c-low-level-curroduction-part-3-the-stack/


我认为有两个决定因素

1
2
1) Scope of variable
2) Performance.

在大多数情况下,我更喜欢使用stack,但如果需要访问范围外的变量,可以使用heap。

为了在使用堆的同时提高性能,您还可以使用创建堆块的功能,这有助于获得性能,而不是在不同的内存位置分配每个变量。