在C ++中正确使用堆栈和堆?


122

我已经编程了一段时间了,但主要是Java和C#。我从来没有真正需要自己管理内存。我最近开始用C ++进行编程,对于何时将内容存储在堆栈上以及何时将它们存储在堆上,我有些困惑。

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


Answers:


242

不,堆栈和堆之间的区别不是性能。它的生命周期:函数内的任何局部变量(任何您不使用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;
}

为了更清楚地了解栈是什么,请从另一端入手-而不是尝试从高级语言的角度了解栈的作用,而是查找“调用栈”和“调用约定”,然后看一下当您调用函数时,机器确实可以运行。计算机内存只是一系列地址;“堆”和“堆栈”是编译器的发明。


7
可以肯定地说,大小可变的信息通常会出现在堆上。我知道的唯一例外是C99中的VLA(支持有限)和alloca()函数,即使C程序员也经常误解。
丹·奥尔森,2009年

10
很好的解释,尽管在频繁分配和/或释放的多线程方案中,堆争用点,因此会影响性能。尽管如此,范围几乎始终是决定因素。
彼得

18
当然,new / malloc()本身是一个缓慢的操作,与任意堆行相比,堆栈更可能位于dcache中。这些是真正的考虑因素,但通常仅次于寿命问题。
Crashworks,2009年

1
是真的“计算机存储器只是一系列地址;“堆”和“堆栈”是编译的发明”?我在很多地方都读到堆栈是计算机内存的一个特殊区域。
Vineeth Chitteti 2014年

2
@kai这是一种可视化的方法,但从物理上讲不一定是正确的。操作系统负责分配应用程序的堆栈和堆。编译器也负责,但主要是依靠OS来完成的。堆栈受限制,而堆不受限制。这是由于OS处理这些内存地址的方式更结构化,以便多个应用程序可以在同一系统上运行。堆并不是唯一的选择,但是通常它们是大多数开发人员关注的仅有的两个。
tsturzl 2015年

42

我会说:

如果可以的话,将其存储在堆栈中。

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

因此,优先选择堆栈而不是堆。无法将某些内容存储在堆栈中的一些可能原因是:

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

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


1
通常最好将堆上的内容多于几个KB。我不知道具体细节,但我不记得曾经使用过“几兆”的堆栈。
丹·奥尔森,2009年

2
一开始我不会与用户有关。对于用户来说,即使STL确实将内容存储在堆中,向量和列表也似乎是在堆栈上分配的。问题似乎更多地在于决定何时显式调用new / delete。
大卫·罗德里格斯(DavidRodríguez)-德里贝斯

1
丹:我已经在32位linux下将2个演出(是,在GIGS中为G)放入堆栈中。堆栈限制取决于操作系统。
先生(Mr.Ree)2009年

6
米尔:任天堂DS堆栈为16 KB。某些堆栈限制取决于硬件。
蚂蚁

Ant:所有堆栈都依赖于硬件,操作系统以及编译器。
Viliami

24

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

std::vector<int> v(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)。

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

在所有最复杂的情​​况下,您可能会将此想法带到一个极端,并使用智能指针类(例如shared_ptrtr1中已经存在的指针)。(尽管我认为如果您似乎需要它,则可能已经超出了标准C ++的适用范围)。


6

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


5

除了其他答案外,还可能涉及性能,至少一点点。除非与您无关,否则您不必担心这一点,但是:

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

要考虑的另一件事(再次,实际上只有在出现问题时才重要)是通常堆栈大小是固定的,并且可能比堆大小小得多。因此,如果要分配大对象或许多小对象,则可能要使用堆。如果堆栈空间不足,运行时将抛出站点名义异常。通常这没什么大不了的,但是要考虑另一件事。


堆和堆栈都是页面虚拟内存。与在新内存中进行映射相比,堆搜索时间非常快。在32位Linux下,我可以将> 2gig放入堆栈。在Macs下,我认为堆栈硬限制为65Meg。
Ree先生

3

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

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

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

资料来源:堆栈不超过8KB的Linux内核!


供其他读者参考:(A)这里的“应该”纯粹是用户的个人观点,最多是从许多用户不太可能遇到(递归)的1次引用和1种情况中得出的。此外,(B)标准库提供了std::unique_ptr,它应该比任何外部库(如Boost)都优先使用(尽管随着时间的推移它确实可以将内容提供给标准)。
underscore_d


1

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


4
我怀疑他是在问什么时候放东西,而不是怎么放。
史蒂夫·罗

0

我认为有两个决定因素

1) Scope of variable
2) Performance.

在大多数情况下,我希望使用堆栈,但是如果您需要访问范围外的变量,则可以使用堆。

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


0

可能已经很好地回答了。我想向您指出以下系列文章,以更深入地了解底层细节。亚历克斯·达比(Alex Darby)有一系列文章,其中将带您调试器。这是关于堆栈的第3部分。 http://www.altdevblogaday.com/2011/12/14/cc-low-level-curriculum-part-3-the-stack/


该链接似乎已失效,但是检查Internet Archive Wayback Machine表示它仅谈论堆栈,因此不回答此处关于堆栈的特定问题。-1
underscore_d
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.