在C ++中适当的堆栈和堆使用?

时间:2009-03-01 05:32:24

标签: c++ memory-management stack heap c++-faq

我已经编程了一段时间,但主要是Java和C#。我实际上从来没有必须自己管理内存。我最近开始使用C ++进行编程,我有点困惑于何时应该将内容存储在堆栈中以及何时将它们存储在堆上。

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

10 个答案:

答案 0 :(得分:240)

不,堆栈和堆之间的区别不是性能。它的生命周期:函数内部的任何局部变量(任何不是malloc()或new的东西)都存在于堆栈中。从函数返回时它会消失。如果你想要的东西比声明它的函数更长寿,你必须在堆上分配它。

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;
}

为了更清楚地了解堆栈是什么,从另一端来看 - 而不是试图理解堆栈在高级语言方面的作用,查找“调用堆栈”和“调用约定”并且看看当你调用一个函数时机器真正做了什么。计算机内存只是一系列地址; “堆”和“堆栈”是编译器的发明。

答案 1 :(得分:42)

我会说:

如果你可以把它存放在堆栈上。

如果需要,将其存储在堆上。

因此,更喜欢堆栈到堆。您无法在堆栈中存储某些内容的一些可能原因是:

  • 太大了 - 在32位操作系统上的多线程程序中,堆栈有一个很小且固定的(至少在线程创建时)大小(通常只有几兆)。这样你就可以创建大量的线程对于64位程序或单线程(Linux无论如何)程序,这不是一个主要问题。在32位Linux下,单线程程序通常使用动态堆栈,这些堆栈可以继续增长,直到达到顶部堆。
  • 您需要在原始堆栈框架范围之外访问它 - 这才是真正的主要原因。

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

答案 2 :(得分:24)

它比其他答案暗示的更微妙。根据您的声明方式,堆栈上的数据与堆上的数据之间没有绝对的区别。例如:

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

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

啊,但是(其他答案显示)该存储的生命周期受vector本身的生命周期的限制,这是基于堆栈的,所以它的实现方式没有区别 - 我们只能将其视为具有值语义的基于堆栈的对象。

不是这样。假设函数是:

void GetSomeNumbers(std::vector<int> &result)
{
    std::vector<int> v(10);

    // fill v with numbers

    result.swap(v);
}

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

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

如果你这样做,你可以把程序中的所有变量看作是简单的值类型,并且完全忘记堆(除了为某些堆数据编写一个类似于值的新包装类时,应该不寻常)。

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

a = b;

像这样交换它们:

a.swap(b);

因为它更快,并且不会抛出异常。唯一的要求是您不需要b继续保持相同的值(它将取代a的值,这将在a = b中被删除。 / p>

缺点是这种方法迫使您通过输出参数而不是实际返回值从函数返回值。但是他们用rvalue references在C ++ 0x中修复它。

在最复杂的情​​况下,你会把这个想法带到一般极端并使用智能指针类,例如已经在tr1中的shared_ptr。 (虽然我认为如果你似乎需要它,你可能已经超出了标准C ++的适用性的最佳位置。)

答案 3 :(得分:6)

如果需要在创建它的函数范围之外使用它,也会在堆上存储项目。与堆栈对象一起使用的一个习惯称为RAII - 这涉及使用基于堆栈的对象作为资源的包装器,当对象被销毁时,资源将被清除。基于堆栈的对象更容易跟踪何时抛出异常 - 您不必担心在异常处理程序中删除基于堆的对象。这就是为什么在现代C ++中通常不使用原始指针的原因,你可以使用一个智能指针,它可以是一个基于堆栈的包装器,用于指向基于堆的对象的原始指针。

答案 4 :(得分:5)

要添加其他答案,它也可以是性能,至少是一点点。除非它与您相关,否则您不应该担心这一点,但是:

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

要考虑的另一件事(再次,如果它成为一个问题真的很重要)是通常堆栈大小是固定的,并且可以远低于堆大小。因此,如果您要分配大对象或许多小对象,您可能希望使用堆;如果你的堆栈空间不足,运行时将抛出网站名称异常。通常不是什么大问题,但需要考虑另一件事。

答案 5 :(得分:3)

Stack更高效,更易于管理范围数据。

但堆应该用于大于 KB 的任何东西(在C ++中很容易,只需在堆栈上创建一个boost::scoped_ptr来保存指针到分配的内存)。

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

:堆栈不超过8KB的Linux内核!

答案 6 :(得分:2)

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

A Heap of Problems

答案 7 :(得分:1)

是否在堆上或堆栈上进行分配的选择取决于您的变量的分配方式。如果使用“新”调用动态分配内容,则从堆中分配。如果将某些内容分配为全局变量,或者将其作为函数中的参数分配,则将其分配到堆栈中。

答案 8 :(得分:0)

在我看来,有两个决定因素

1) Scope of variable
2) Performance.

我更喜欢在大多数情况下使用堆栈,但如果您需要访问变量外部作用域,则可以使用堆。

要在使用堆时增强性能,您还可以使用该功能创建堆块,这有助于获得性能,而不是将每个变量分配到不同的内存位置。

答案 9 :(得分:0)

可能这个问题得到了很好的回答。我想指出以下系列文章,以便更深入地了解低级细节。 Alex Darby有一系列文章,他带着调试器指导您。这是关于Stack的第3部分。 http://www.altdevblogaday.com/2011/12/14/c-c-low-level-curriculum-part-3-the-stack/