为什么我们使用堆来存储内存?

时间:2016-11-20 06:10:57

标签: c++ algorithm heap

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

编辑: 那么,使用了什么结构/算法 ARE 。它是如何(如果)与堆算法相关的?

无需与" stack"进行比较分配(它遍布整个网络),或讨论C ++语义 - 今天的记忆堆是什么?

3 个答案:

答案 0 :(得分:2)

听起来好像是heap"heap"混淆了。堆数据结构很少用于动态内存分配。

现在,重新:为什么动态内存分配?有时您不知道需要多少内存,并且不想仅仅为了以防分配大量缓冲区。在堆上分配允许在运行时更改存储空间量。

答案 1 :(得分:2)

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

从历史上看,早期的计算机系统只有很少的内存,而早期的操作系统只能管理很少的内存。早期的64K(即千字节,不是兆字节或千兆字节)等数量实际上是大量内存,而早期操作系统的设计能够支持不超过640K的程序运行。那就是总RAM。

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

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

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

答案 2 :(得分:1)

在这种情况下,"堆"问题与称为堆的数据结构不同。

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

对于C ++中的动态分配,您经常使用newdelete(至少间接使用,例如容器使用的std::allocator<T>)。有些人有时将此称为&#34;堆&#34;同样,但是那些试图变得更加恰当的人更经常将其称为“免费商店”。 (这是标准中使用的措辞)。

然而,无论哪种方式,都很少(如果有的话)涉及实际的堆。既没有指定(或意图引用)用于管理内存的数据结构。

对于它的价值,与我用于管理内存的实际堆最接近的是使用&#34;伙伴系统&#34;分配器(但我在实际使用中看到的相对较少,尽管Knuth详细介绍了它们。

关于使用什么结构,一个非常常见的结构只是一个链的列表。每个块至少会记录自己的大小。

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

  • 最适合意味着找到足够大的最小空闲块以满足请求。为此,您通常会按照大小按升序排序自由块列表,因此第一个块足够大也是最合适的。
  • 最差的意味着总是从最大的块开始。为此,您通常会按大小按降序排列空闲块列表。这样,列表中的第一个块是最大的,因此您总是要么使用它,要么分配失败(或者您需要做更多来自OS的分配)。在大多数情况下,您仍然需要进行一些列表遍历,因为您将最大的块分成两个:您分配给用户的那个,以及剩下的那个,然后您重新分配按新大小按顺序插入列表。
  • 第一个拳头意味着您遍历列表并使用可以满足分配的第一个块。

在所有情况下,您通常都有一个块拆分策略。也就是说,如果分配要求的大小小于您选择的块的大小,您可以选择保留该块,并只给用户一点额外的内存,或者将该块拆分为两个:一个&# 39;用于满足分配,另一个回到自由列表。在大多数情况下,你试图避免创建微小的块,所以除非&#34;遗留下来&#34;部分大于某个特定大小,您只需保留原始块。

如果他们不太考虑事情,大多数人的第一直觉就是使用最合适的。最合适的问题是,当你分割一个块时,它会产生最小的剩余部分,因此你倾向于最终得到许多不能满足任何分配的小块。如果您确实使用它,通常需要设置一个相当高的阈值,您只需保持一个块完整而不是分割它。

最适合试图抵消这种情况。虽然它可能会分割最大的块,但它往往会留下最大的块,因此它最有可能被使用。

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

如果您按照某种顺序保留空闲块,那么使用某种树或其他树来存储块的明显修改也是如此,因此您可以在大致对数时间内找到块而不是线性时间。