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