在 C++ 中正确使用堆栈和堆?
Posted
技术标签:
【中文标题】在 C++ 中正确使用堆栈和堆?【英文标题】:Proper stack and heap usage in C++? 【发布时间】:2010-10-10 14:27:59 【问题描述】:我已经编程了一段时间,但主要是 Java 和 C#。我从来没有真正需要自己管理内存。我最近开始使用 C++ 进行编程,对于何时应该将内容存储在堆栈中以及何时将它们存储在堆中感到有些困惑。
我的理解是,经常访问的变量应该存放在栈和对象上,很少使用的变量,大数据结构都应该存放在堆上。这是正确的还是我的错误?
【问题讨论】:
When is it best to use the stack instead of the heap and vice versa? 的可能重复项 【参考方案1】:不,堆栈和堆之间的区别不在于性能。它的生命周期:函数内的任何局部变量(任何你不使用 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;
为了更清楚地了解堆栈是什么,请从另一端了解堆栈——不要试图从高级语言的角度理解堆栈的作用,而是查找“调用堆栈”和“调用约定”当你调用一个函数时,看看机器到底做了什么。计算机内存只是一系列地址; “堆”和“栈”是编译器的发明。
【讨论】:
添加可变大小的信息通常放在堆上是安全的。我知道的唯一例外是 C99 中的 VLA(支持有限)和即使 C 程序员也经常误解的 alloca() 函数。 很好的解释,尽管在频繁分配和/或释放的多线程场景中,堆是一个争用点,因此会影响性能。尽管如此,范围几乎总是决定因素。 当然,new/malloc() 本身是一个缓慢的操作,并且堆栈更有可能在 dcache 中而不是任意堆行。这些是真正的考虑因素,但通常次要于寿命问题。 是真的“计算机内存只是一系列地址;“堆”和“堆栈”是编译的发明吗??我在很多地方都读到堆栈是我们计算机内存的一个特殊区域。 @kai 这是一种可视化的方式,但从物理上讲不一定是真的。操作系统负责分配应用程序的堆栈和堆。编译器也有责任,但主要是依赖操作系统来完成。栈是有限的,堆不是。这是由于操作系统处理将这些内存地址排序为更结构化的方式,以便多个应用程序可以在同一系统上运行。堆和栈并不是唯一的,但它们通常是大多数开发人员关心的仅有的两个。【参考方案2】:我会说:
如果可以,将其存储在堆栈中。
如果需要,将其存储在堆中。
因此,比起堆,更喜欢堆栈。您无法在堆栈中存储某些内容的一些可能原因是:
太大了 - 在 32 位操作系统上的多线程程序中,堆栈具有较小且固定的(至少在线程创建时)大小(通常只有几兆。这样您就可以创建大量线程在不耗尽地址空间的情况下。对于 64 位程序或单线程(无论如何是 Linux)程序,这不是主要问题。在 32 位 Linux 下,单线程程序通常使用动态堆栈,该堆栈可以不断增长,直到达到堆。 您需要在原始堆栈框架范围之外访问它 - 这确实是主要原因。使用合理的编译器,可以在堆上分配非固定大小的对象(通常是在编译时大小未知的数组)。
【讨论】:
任何超过几 KB 的东西通常最好放在堆上。我不知道具体细节,但我不记得曾经使用过“几兆”的堆栈。 一开始我不会关心用户。对于用户来说,向量和列表似乎是在堆栈上分配的,即使 STL 确实将内容存储在堆上。问题似乎更多的是决定何时明确调用 new/delete。 Dan:我已经在 32 位 linux 下将 2 个 gig(是的,G 与 GIGS 相同)放到了堆栈上。堆栈限制取决于操作系统。 mrree:Nintendo DS 堆栈为 16 KB。一些堆栈限制取决于硬件。 Ant:所有的栈都依赖于硬件,依赖于操作系统,也依赖于编译器。【参考方案3】:它比其他答案建议的更微妙。根据您声明的方式,堆栈上的数据和堆上的数据之间没有绝对的区别。例如:
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++ 的适用范围。)
【讨论】:
【参考方案4】:如果项目需要在创建它的函数范围之外使用,您也可以将项目存储在堆上。与堆栈对象一起使用的一个习惯用法称为 RAII - 这涉及使用基于堆栈的对象作为资源的包装器,当对象被销毁时,资源将被清理。基于堆栈的对象更容易跟踪您何时可能引发异常 - 您无需担心在异常处理程序中删除基于堆的对象。这就是为什么现代 C++ 通常不使用原始指针的原因,您可以使用智能指针,它可以是基于堆栈的包装器,用于指向基于堆的对象的原始指针。
【讨论】:
【参考方案5】:要补充其他答案,它也可能与性能有关,至少是一点点。除非它与您相关,否则您不必担心这一点,但是:
在堆中分配需要找到一个跟踪内存块,这不是一个恒定时间的操作(并且需要一些周期和开销)。随着内存碎片化和/或您接近使用 100% 的地址空间,这可能会变慢。另一方面,堆栈分配是固定时间的,基本上是“免费”操作。
另一件需要考虑的事情(同样,只有当它成为问题时才真正重要)是通常堆栈大小是固定的,并且可以远低于堆大小。因此,如果您要分配大对象或许多小对象,您可能希望使用堆;如果您用完堆栈空间,运行时将抛出站点名义异常。通常没什么大不了的,但要考虑另一件事。
【讨论】:
堆和栈都是分页的虚拟内存。与映射到新内存所需的时间相比,堆搜索时间快得惊人。在 32 位 Linux 下,我可以将 >2gig 放到我的堆栈中。在 Mac 下,我认为堆栈被硬限制为 65Meg。【参考方案6】:堆栈更高效,更易于管理范围数据。
但是堆应该用于大于几个 KB的东西(这在 C++ 中很容易,只需在堆栈上创建一个 boost::scoped_ptr
来保存指向分配的内存)。
考虑一个不断调用自身的递归算法。很难限制或猜测总堆栈使用量!而在堆上,分配器(malloc()
或 new
)可以通过返回 NULL
或 throw
来指示内存不足。
来源:堆栈不大于8KB的Linux内核!
【讨论】:
供其他读者参考:(A)这里的“应该”纯粹是用户的个人意见,最多来自1个引用和1个许多用户不太可能遇到的场景(递归)。此外,(B) 标准库提供了std::unique_ptr
,它应该优于任何外部库,如 Boost(尽管随着时间的推移,它确实会向标准提供东西)。【参考方案7】:
为了完整起见,您可以阅读 Miro Samek 关于在嵌入式软件上下文中使用堆的问题的文章。
A Heap of Problems
【讨论】:
【参考方案8】:是在堆上分配还是在堆栈上分配是为您量身定做的,这取决于您的变量是如何分配的。如果您使用“新”调用动态分配某些内容,则您是从堆中分配的。如果您将某些东西分配为全局变量,或者作为函数中的参数,则它会分配在堆栈上。
【讨论】:
我怀疑他问的是什么时候把东西放在堆上,而不是如何。【参考方案9】:在我看来有两个决定因素
1) Scope of variable
2) Performance.
在大多数情况下我更喜欢使用堆栈,但如果您需要访问范围之外的变量,您可以使用堆。
为了在使用堆时提高性能,您还可以使用创建堆块的功能来帮助提高性能,而不是将每个变量分配到不同的内存位置。
【讨论】:
【参考方案10】:这可能已经得到了很好的回答。我想向您指出以下系列文章,以更深入地了解底层细节。 Alex Darby 有一系列文章,他将通过调试器引导您完成。这是关于堆栈的第 3 部分。 http://www.altdevblogaday.com/2011/12/14/c-c-low-level-curriculum-part-3-the-stack/
【讨论】:
该链接似乎已失效,但检查 Internet Archive Wayback Machine 表明它仅讨论堆栈,因此没有回答堆栈与 堆。 -1以上是关于在 C++ 中正确使用堆栈和堆?的主要内容,如果未能解决你的问题,请参考以下文章