为啥我们使用堆来存储内存?

Posted

技术标签:

【中文标题】为啥我们使用堆来存储内存?【英文标题】:Why do we use heap to store memory?为什么我们使用堆来存储内存? 【发布时间】:2016-11-20 06:10:57 【问题描述】:

如果这听起来像是一个幼儿园问题,那么请原谅我;)但是,在 C++ 中,一个堆用于内存分配,因为......曾经(至少是 80 年代)。它是这项工作的最佳算法,还是我们只是被它困住了(就像 javascript 一样......)?是否所有(非嵌入式)操作系统都使用堆来存储内存?

编辑: 那么,ARE 使用了哪些结构/算法。以及它与堆算法有什么关系?

无需与“堆栈”分配进行比较(网络上到处都是),或讨论 C++ 语义 - 今天的内存堆是什么?

【问题讨论】:

如今,内存分配“堆”几乎从未被构造为任何类型的最大堆或最小堆。我认为真正早期的实现之一使用了堆数据结构,而这个术语只是卡住了。 堆,与内存分配有关,不是算法,也不是数据结构。它只是“动态存储”的别称。 【参考方案1】:

听起来您将heap 与"heap" 混淆了。堆数据结构很少(如果有的话)用于动态内存分配。

现在,回复:为什么要动态内存分配?有时您不知道需要多少内存,也不想分配大量缓冲区以防万一。在堆上分配允许您在运行时更改的存储空间量。

【讨论】:

【参考方案2】:

在内存分配的上下文中,堆不是数据结构或算法。它更符合英语定义,英语定义本质上是某个地方的非结构化事物组。

从历史上看,早期的计算机系统的内存量非常少,而早期的操作系统只管理少量内存。在早期,像 64K(即千字节,而不是兆字节或千兆字节)这样的数量实际上是相当大的内存量,早期的操作系统被设计为能够支持不超过 640K 来运行程序。这是总内存。

当某些计算机具有两个或更多独立的物理内存芯片组时,这实际上是一种创新。其中一个用于在内存中运行程序,其他用于存储程序所需的数据,其访问速度比从磁盘读取数据的速度更快。这两个内存区域分别被称为堆栈和堆。需要在特殊设备驱动程序的帮助下访问堆。这种可用的堆内存量通常(并不总是)比堆栈内存大得多。

实际上,在 C 和 C++ 的早期实现中,静态和自动变量通常使用堆栈内存,而动态分配的内存(malloc() 等)使用堆。尽管现在主要是学术上的区别(例如,堆和堆栈限制被设置为逻辑配额,而不是反映物理上可用的内存库),但名称仍然存在。

因此,在现代 C 和 C++ 中,“堆”的正确术语是“动态分配的内存”。动态内存分配(例如 malloc() 之类的函数)不一定使用任何称为“堆”的内存区域(尽管很明显,主机系统必须使用某种数据结构来跟踪内存分配和释放)。

【讨论】:

我认为您的答案错过了堆使用的结构。历史和堆栈比较是众所周知的...... 这不仅没有抓住重点,而且它所说的相当多的内容都是完全错误的。【参考方案3】:

在这种情况下,所讨论的“堆”与称为堆的数据结构不同。

通常“堆”是指由malloc/realloc/free 管理的内存。这些通常在合理编写的 C++ 中很少使用(如果有的话)。

对于 C++ 中的动态分配,您更经常使用newdelete(至少是间接的,例如通过容器使用std::allocator<T>)。有些人有时也将其称为“堆”,但试图更恰当的人更经常将其称为“免费存储”(这是标准中使用的措辞)。

然而,无论如何,很少(如果有的话)涉及实际的堆。两者均未指定(或旨在引用)用于管理内存的数据结构。

对于它的价值,我见过的用于管理内存的最接近实际堆的东西是使用“伙伴系统”分配器(但我在实际使用中看到的这些分配器相对较少,即使 Knuth 进入关于它们的相当多的细节。

至于使用什么结构,一种极为常见的结构就是块的链表。每个块至少会记录自己的大小。

常见的基本算法有最佳拟合、最差拟合和首次拟合。

最佳匹配意味着找到足够大以满足请求的最小空闲块。为此,您通常会将空闲块列表按大小升序排列,因此第一个足够大的块也是最合适的。 最差拟合意味着总是从最大的块开始。为此,您通常会保留按大小降序排列的空闲块列表。这样,要么列表中的第一个块是最大的,所以你要么总是使用它,要么分配失败(或者你需要从操作系统做更多的分配)。在大多数情况下,您仍然需要进行一些列表遍历,因为您将最大的块分成两部分:一个分配给用户,另一个是剩余的,然后您按顺序将其重新插入到列表中新尺寸。 第一个拳头意味着您遍历列表并使用第一个可以满足分配的块。

在所有情况下,您通常都有块拆分策略。也就是说,如果分配要求的大小小于您选择的块,您可以选择保留该块原样,只给用户一点额外的内存,或者将该块分成两部分:一个已使用满足分配,另一个回到空闲列表。在大多数情况下,您会尽量避免创建微小的块,因此除非“剩余”部分大于某个特定大小,否则您只需保持原始块不变。

如果他们对事情想得不多,大多数人的第一直觉是使用最佳匹配。最佳拟合的问题在于,当您拆分一个块时,它会产生最小的剩余部分,因此您倾向于最终得到许多无法满足任何分配的小块。如果您确实使用它,您通常希望设置一个相当高的阈值,在该阈值中您将保持块完整而不是拆分它。

最差拟合试图抵消这一点。虽然它可能会拆分最大的块,但它往往会留下最大的剩余块,因此它最有可能可用。

还有混合,例如精确拟合、最差拟合。也就是说,不是总是使用最大的块,而是首先寻找一个完全适合(或足够接近以至于不会拆分该块)的块,并且只有在失败时才拆分最大的块。

如果您以某种顺序保留空闲块,还有使用某种树来存储块的明显修改,因此您可以在大致对数时间而不是线性时间中找到一个块。

【讨论】:

这确实令人困惑......我在生活中写了一些 std:allocators,所以是的,我知道它的全部内容......

以上是关于为啥我们使用堆来存储内存?的主要内容,如果未能解决你的问题,请参考以下文章

让你知道智能指针的魅力

kafka如何做到磁盘读写比内存读写还快?

C++ 一篇文章让你知道智能指针的魅力

(为啥)我们需要在 RDD 上调用缓存还是持久化

为啥不建议使用NSUserDefault存储大量数据

为啥我们需要虚拟内存?