我已经编程了一段时间,但它主要是Java和C#.我实际上从来没有必须自己管理内存.我最近开始用C++编程,我有点困惑的是什么时候我应该把东西存放在堆栈上以及何时将它们存储在堆上.
我的理解是,非常频繁访问的变量应该存储在堆栈中,对象,很少使用的变量和大型数据结构都应该存储在堆上.这是正确的还是我错了?
不,堆栈和堆之间的区别不是性能.它的生命周期:函数内部的任何局部变量(任何不是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; }
为了更清楚地理解堆栈是什么,从另一端来看它 - 而不是试图理解堆栈在高级语言方面的作用,查找"调用堆栈"和"调用约定",看看是什么当你调用一个函数时,机器真的会这样做.计算机内存只是一系列地址; "堆"和"堆栈"是编译器的发明.
我会说:
如果可以的话,将它存储在堆栈中.
如果需要,将其存储在堆上.
因此,更喜欢堆栈到堆.您无法在堆栈中存储某些内容的一些可能原因是:
它太大了 - 在32位操作系统上的多线程程序中,堆栈有一个小而固定的(至少在线程创建时)大小(通常只有几兆).这样你就可以创建大量线程而不会耗费地址对于64位程序或单线程(Linux无论如何)程序,这不是一个主要问题.在32位Linux下,单线程程序通常使用动态堆栈,这些堆栈可以保持增长直到它们到达堆顶部.
您需要在原始堆栈框架范围之外访问它 - 这才是真正的主要原因.
使用合理的编译器,可以在堆上分配非固定大小的对象(通常是在编译时未知大小的数组).
它比其他答案暗示的更微妙.根据您的声明方式,堆栈上的数据与堆上的数据之间没有绝对的区别.例如:
std::vectorv(10);
在函数体中,它声明vector
堆栈上有十个整数的(动态数组).但是由它管理的存储vector
不在堆栈中.
啊,但是(其他答案提示)该存储的生命周期受其vector
自身的生命周期的限制,这是基于堆栈的,所以它实现的方式没有区别 - 我们只能将其视为基于堆栈的对象具有价值语义.
不是这样.假设函数是:
void GetSomeNumbers(std::vector&result) { std::vector v(10); // fill v with numbers result.swap(v); }
因此,任何具有swap
函数的函数(以及任何复杂值类型都应该具有一个函数)都可以作为一种对某些堆数据的可重新引用的引用,在一个保证该数据的单个所有者的系统下.
因此,现代C++方法永远不会将堆数据的地址存储在裸局部指针变量中.所有堆分配必须隐藏在类中.
如果你这样做,你可以把你程序中的所有变量看作是简单的值类型,并且完全忘记堆(除了为某些堆数据编写一个类似值的包装类时,这应该是不寻常的) .
您只需要保留一些特殊的知识来帮助您优化:在可能的情况下,而不是像这样将一个变量分配给另一个变量:
a = b;
像这样交换它们:
a.swap(b);
因为它更快,并且不会抛出异常.唯一的要求是你不需要b
继续保持相同的值(它将获得相应a
的值,这将被破坏a = b
).
缺点是这种方法迫使您通过输出参数而不是实际返回值从函数返回值.但他们正在使用rvalue引用在C++ 0x中修复它.
在最复杂的情况下,你会把这个想法带到一般极端并使用智能指针类,例如shared_ptr
已经在tr1中.(虽然我认为如果你似乎需要它,你可能已经超出了标准C++的适用性的最佳位置.)
如果需要在创建它的函数范围之外使用它,也可以在堆上存储项目.与堆栈对象一起使用的一个习惯称为RAII - 这涉及使用基于堆栈的对象作为资源的包装器,当对象被销毁时,资源将被清除.基于堆栈的对象更容易跟踪何时抛出异常 - 您不必担心在异常处理程序中删除基于堆的对象.这就是为什么原始指针通常不在现代C++中使用的原因,你可以使用一个智能指针,它可以是一个基于堆栈的包装器,用于指向基于堆的对象的原始指针.
要添加到其他答案,它也可以是性能,至少一点点.除非它与您相关,否则您不应该担心这一点,但是:
在堆中分配需要找到跟踪内存块,这不是一个恒定时间操作(并且需要一些周期和开销).随着内存碎片化,和/或您接近使用100%的地址空间,这可能会变慢.另一方面,堆栈分配是恒定时间,基本上是"免费"操作.
要考虑的另一件事(再次,如果它成为一个问题,真的唯一重要)是通常堆栈大小是固定的,并且可以远低于堆大小.因此,如果您要分配大对象或许多小对象,您可能希望使用堆; 如果你的堆栈空间不足,运行时将抛出网站名称异常.通常不是什么大不了的事,但需要考虑的另一件事.