【发布时间】:2010-10-10 14:27:59
【问题描述】:
我已经编程了一段时间,但主要是 Java 和 C#。我从来没有真正需要自己管理内存。我最近开始使用 C++ 进行编程,对于何时应该将内容存储在堆栈中以及何时将它们存储在堆中感到有些困惑。
我的理解是,经常访问的变量应该存放在栈和对象上,很少使用的变量,大数据结构都应该存放在堆上。这是正确的还是我的错误?
【问题讨论】:
标签: c++ memory-management stack heap-memory c++-faq
我已经编程了一段时间,但主要是 Java 和 C#。我从来没有真正需要自己管理内存。我最近开始使用 C++ 进行编程,对于何时应该将内容存储在堆栈中以及何时将它们存储在堆中感到有些困惑。
我的理解是,经常访问的变量应该存放在栈和对象上,很少使用的变量,大数据结构都应该存放在堆上。这是正确的还是我的错误?
【问题讨论】:
标签: c++ memory-management stack heap-memory c++-faq
不,堆栈和堆之间的区别不在于性能。它的生命周期:函数内的任何局部变量(任何你不使用 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;
}
为了更清楚地了解堆栈是什么,请从另一端了解堆栈——不要试图从高级语言的角度理解堆栈的作用,而是查找“调用堆栈”和“调用约定”当你调用一个函数时,看看机器到底做了什么。计算机内存只是一系列地址; “堆”和“栈”是编译器的发明。
【讨论】:
我会说:
如果可以,将其存储在堆栈中。
如果需要,将其存储在堆中。
因此,比起堆,更喜欢堆栈。您无法在堆栈中存储某些内容的一些可能原因是:
使用合理的编译器,可以在堆上分配非固定大小的对象(通常是在编译时大小未知的数组)。
【讨论】:
它比其他答案建议的更微妙。根据您声明的方式,堆栈上的数据和堆上的数据之间没有绝对的区别。例如:
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 references 在 C++0x 中修复该问题。
在最复杂的情况下,您会将这个想法发挥到极致,并使用智能指针类,例如已经在 tr1 中的shared_ptr。 (尽管我认为如果您似乎需要它,那么您可能已经超出了标准 C++ 的适用范围。)
【讨论】:
如果项目需要在创建它的函数范围之外使用,您也可以将项目存储在堆上。与堆栈对象一起使用的一个习惯用法称为 RAII - 这涉及使用基于堆栈的对象作为资源的包装器,当对象被销毁时,资源将被清理。基于堆栈的对象更容易跟踪您何时可能引发异常 - 您无需担心在异常处理程序中删除基于堆的对象。这就是为什么现代 C++ 通常不使用原始指针的原因,您可以使用智能指针,它可以是基于堆栈的包装器,用于指向基于堆的对象的原始指针。
【讨论】:
要补充其他答案,它也可能与性能有关,至少是一点点。除非它与您相关,否则您不必担心这一点,但是:
在堆中分配需要找到一个跟踪内存块,这不是一个恒定时间的操作(并且需要一些周期和开销)。随着内存碎片化和/或您接近使用 100% 的地址空间,这可能会变慢。另一方面,堆栈分配是固定时间的,基本上是“免费”操作。
另一件需要考虑的事情(同样,只有当它成为问题时才真正重要)是通常堆栈大小是固定的,并且可以远低于堆大小。因此,如果您要分配大对象或许多小对象,您可能希望使用堆;如果您用完堆栈空间,运行时将抛出站点名义异常。通常没什么大不了的,但要考虑另一件事。
【讨论】:
堆栈更高效,更易于管理范围数据。
但是堆应该用于大于几个 KB的东西(这在 C++ 中很容易,只需在堆栈上创建一个 boost::scoped_ptr 来保存指向分配的内存)。
考虑一个不断调用自身的递归算法。很难限制或猜测总堆栈使用量!而在堆上,分配器(malloc() 或 new)可以通过返回 NULL 或 throw 来指示内存不足。
来源:堆栈不大于8KB的Linux内核!
【讨论】:
std::unique_ptr,它应该优于任何外部库,如 Boost(尽管随着时间的推移,它确实会向标准提供东西)。
为了完整起见,您可以阅读 Miro Samek 关于在嵌入式软件上下文中使用堆的问题的文章。
【讨论】:
是在堆上分配还是在堆栈上分配是为您量身定做的,这取决于您的变量是如何分配的。如果您使用“新”调用动态分配某些内容,则您是从堆中分配的。如果您将某些东西分配为全局变量,或者作为函数中的参数,则它会分配在堆栈上。
【讨论】:
在我看来有两个决定因素
1) Scope of variable
2) Performance.
在大多数情况下我更喜欢使用堆栈,但如果您需要访问范围之外的变量,您可以使用堆。
为了在使用堆时提高性能,您还可以使用创建堆块的功能来帮助提高性能,而不是将每个变量分配到不同的内存位置。
【讨论】:
这可能已经得到了很好的回答。我想向您指出以下系列文章,以更深入地了解底层细节。 Alex Darby 有一系列文章,他将通过调试器引导您完成。这是关于堆栈的第 3 部分。 http://www.altdevblogaday.com/2011/12/14/c-c-low-level-curriculum-part-3-the-stack/
【讨论】: