[英]Proper stack and heap usage in 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
函数(以及任何复杂值类型都应该具有swap
函数)的任何东西都可以作为对某些堆数据的一种可重新绑定的引用。
因此,现代 C++ 方法永远不会将堆数据的地址存储在裸局部指针变量中。 所有堆分配必须隐藏在类中。
如果这样做,您可以将程序中的所有变量视为简单的值类型,而完全忘记堆(除非为某些堆数据编写新的类似值的包装类,这应该是不寻常的) .
您只需要保留一些特殊的知识来帮助您进行优化:在可能的情况下,不要像这样将一个变量分配给另一个变量:
a = b;
像这样交换它们:
a.swap(b);
因为它更快,而且不会抛出异常。 唯一的要求是你不需要b
继续保持相同的值(它会得到a
的值,这会在a = b
被丢弃)。
缺点是这种方法迫使您通过输出参数而不是实际返回值从函数返回值。 但是他们在 C++0x 中使用rvalue 引用修复了这个问题。
在最复杂的情况下,您会将这种想法发挥到极致,并使用智能指针类,例如 tr1 中已经存在的shared_ptr
。 (尽管我认为如果您似乎需要它,您可能已经超出了标准 C++ 的适用性最佳点。)
如果需要在创建它的函数的范围之外使用它,您还可以将它存储在堆上。 与堆栈对象一起使用的一个习惯用法称为 RAII - 这涉及使用基于堆栈的对象作为资源的包装器,当对象被销毁时,资源将被清理。 基于堆栈的对象更容易跟踪您何时可能抛出异常 - 您无需担心在异常处理程序中删除基于堆的对象。 这就是为什么现代 C++ 中通常不使用原始指针的原因,您将使用智能指针,它可以是基于堆栈的包装器,用于指向基于堆的对象的原始指针。
要添加到其他答案中,它也可能与性能有关,至少有一点。 并不是说您应该担心这一点,除非它与您相关,但是:
在堆中分配需要找到跟踪内存块,这不是恒定时间操作(并且需要一些周期和开销)。 随着内存变得碎片化,和/或您接近使用 100% 的地址空间,这可能会变慢。 另一方面,堆栈分配是恒定时间的,基本上是“免费”操作。
另一件要考虑的事情(同样,只有在它成为问题时才真正重要)是通常堆栈大小是固定的,并且可以远小于堆大小。 因此,如果您要分配大对象或许多小对象,您可能希望使用堆; 如果堆栈空间用完,运行时将抛出站点名义异常。 通常不是什么大问题,但需要考虑的另一件事。
堆栈更高效,更容易管理范围内的数据。
但是堆应该用于大于几KB 的任何东西(这在 C++ 中很容易,只需在堆栈上创建一个boost::scoped_ptr
来保存指向已分配内存的指针)。
考虑一个不断调用自身的递归算法。 很难限制或猜测总堆栈使用量! 而在堆上,分配器( malloc()
或new
)可以通过返回NULL
或throw
来指示内存不足。
来源:堆栈不大于 8KB 的 Linux Kernel!
为了完整起见,您可以阅读 Miro Samek 关于在嵌入式软件上下文中使用堆的问题的文章。
是在堆上分配还是在堆栈上分配的选择是为您量身定做的,这取决于您的变量是如何分配的。 如果您使用“new”调用动态分配某些内容,则您正在从堆中进行分配。 如果您将某些内容分配为全局变量,或作为函数中的参数,则将其分配在堆栈上。
可能这已经得到了很好的回答。 我想向您指出以下系列文章,以便更深入地了解底层细节。 Alex Darby 有一系列文章,他将带您了解调试器。 这是关于堆栈的第 3 部分。 http://www.altdevblogaday.com/2011/12/14/cc-low-level-curriculum-part-3-the-stack/
在我看来有两个决定因素
1) Scope of variable
2) Performance.
在大多数情况下,我更喜欢使用堆栈,但如果您需要访问范围外的变量,则可以使用堆。
要在使用堆时提高性能,您还可以使用该功能创建堆块,这有助于提高性能,而不是将每个变量分配到不同的内存位置。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.