C++ 是否可以完全避免堆碎片?

C++ 是否可以完全避免堆碎片?,c++,memory-management,C++,Memory Management,例如,如果动态内存的释放总是以与分配相反的方向进行。在这种情况下,是否可以保证堆不会变得支离破碎 从理论上看:对于一个非平凡的应用程序来说,是否存在一些现实的方法来管理内存以完全避免堆碎片?(在堆中的每个原子更改之后,堆是否仍然未被分割?我认为理论上可以实现这一点(假设堆实现也在做“正确的事情”[例如,释放块时立即合并块]) 但在解决实际问题的实际应用中,这是不可能的。当然,使用std::string或std::vector几乎肯定会以“无序方式”分配/释放内存 如果您的场景中堆碎片是一个潜在的

例如,如果动态内存的释放总是以与分配相反的方向进行。在这种情况下,是否可以保证堆不会变得支离破碎


从理论上看:对于一个非平凡的应用程序来说,是否存在一些现实的方法来管理内存以完全避免堆碎片?(在堆中的每个原子更改之后,堆是否仍然未被分割?

我认为理论上可以实现这一点(假设堆实现也在做“正确的事情”[例如,释放块时立即合并块])

但在解决实际问题的实际应用中,这是不可能的。当然,使用
std::string
std::vector
几乎肯定会以“无序方式”分配/释放内存


如果您的场景中堆碎片是一个潜在的问题,那么几乎可以肯定,最好使用一个能够减少/消除这一问题的解决方案(例如,固定大小的bucket分配,不同类型分配的单独堆-这只是许多不同解决方案中的两个)

真正避免堆碎片的唯一方法是自己进行内存管理,跟踪您使用的所有指针,并不时对堆进行碎片整理。但要知道,这是没有做到的一个原因:它没有回报。整理堆碎片所需的代码和性能方面的工作量太大了

只是一段轶事般的历史信息:

旧的MacOS(MacOS-X时代之前!)使用所谓的内存对象句柄:指向实际内存区域的指针列表的指针。这样做的好处是操作系统可以通过修改表中的指针来移动内存对象;句柄的任何引用都将保持不变。但是,每次在系统调用中访问句柄的数据时,都必须锁定句柄。应该提到的是,这是在多核成为主流之前,所以没有真正的并发。对于多线程应用程序,每次都必须锁定句柄,至少如果允许其他线程调用系统。我重复一遍,句柄在MacOS-X中不存在是有原因的。

可以将堆划分为允许特定大小内存分配的区域。这样,堆就不会被分区,但会产生内存消耗开销,并有耗尽可用块的风险,但有时这是合理的(例如,当软件必须24/7/365保持在线时。大小可以是2的幂,或者应用程序最常用的大小,或者只是在第一次分配大小N时按需分配的新内存区域,最大可达一些合理的大小N)

它看起来与此类似(
*
-分配块,
-空闲块)

我不久前在试验该主题时编写的可视化工具:

对于非平凡的应用程序,存在一些实际的方法来管理内存以完全避免堆碎片

是的。分配堆栈上的所有内容。这似乎是一个完美的建议,但我在非平凡的程序中做过


编辑为了绝对清楚和避免疑问,我说的是“堆栈”。我的意思是自动存储,即局部变量堆栈,而不是任何堆栈类。关于这一点的一些评论基本上是迟钝的。

在Java等托管语言中,堆碎片整理一直在发生。这是可能的,因为Jav“指针”实际上是引用,Java知道它们,并且能够在移动堆中的对象时移动它们

<>在C和C++指针中可以是任何东西,它们可以被计算、组合、更新等等。这意味着一旦分配了某个东西,它就不能移动,因为你永远不知道什么东西实际上是指向它的指针,这就立即使碎片整理在一般情况下不可能。
减少碎片的唯一方法是只将相同大小的元素存储在相同的内存区域中,但这是非常不灵活的,对于许多实际编程情况来说是不可行的。

正如delnan上面提到的,另一个问题是虚拟内存页碎片,当有大量分配和空闲时可能会发生内存不足。windows.net应用程序依赖.net的垃圾收集方案来重新打包分配的内存,但这会导致运行程序暂停。我不确定每次暂停的时间有多长。

我的大部分软件设计和编程都是在硬实时系统中进行的。这些是控制炼油厂的非常关键的实时系统,发电厂、钢厂、铜冶炼厂、石油化工厂、石油和天然气管道存储和运输设施。我们不能允许任何内存碎片导致功能丧失或任何控制服务器重新启动,因为其结果将导致最小的财务损失,最坏的情况是灾难性的损坏和数据丢失生活


我们只创建了三个固定的缓冲区大小:小、中、大。启动时,我们将所有内存预分配到这三个精确大小,并通过简单的链表自行管理内存。不需要垃圾收集,但我们当然必须显式分配和释放缓冲区。

对于第一种情况,标准当然不能保证这一点。但如果分配模式如此简单,你可以很容易地自己制作一个性能良好的。第二种情况显然是一个更难解决的问题。就我个人而言,我只(成功地)在特定于应用程序的级别上完成。这并不是一件小事,因为它需要有足够的关于未来分配的信息,以知道将当前分配放在何处。听起来您的分配满足堆栈规则,因此用作堆栈的大块连续内存应该是无碎片的,并满足您的分配需要。@汉斯帕桑A 64(或ra
 size=1: [*][ ][*][*][*][ ][ ][ ][ ][ ]
 size=2: [  ][  ][  ][**][**][  ][  ][  ]
 size=4: [    ][****][    ][    ][    ]
 size=8: [        ][        ][        ]
 ....