5

我们在嵌入式系统环境中使用 C++,基本上不想要任何类型的动态内存分配(例如,请参阅嵌入式应用程序中的内存管理资源,了解我们不这样做的原因)。我们仍然不想缺少一些不错的基于 C++ 的特性,例如 STL 容器和 std::string。对于第一个,我们会在初始化时保留一个特定的大小,并且不会让容器超出其容量。对于后者(std::string),我对如何“安全”地使用它们有点怀疑,因为它们有时会在堆上分配内存。

不过,我发现使用 std::string (以及通常其他堆分配对象)似乎很好的情况:我会在堆栈上分配对象本身(在{}我正在谈论的某个范围内来自 C++)并允许它们分配堆,前提是它们在超出范围时实际上释放了所有保留的内存。

我知道这种方法并不能绝对保证内存碎片的自由,但我觉得如果手头的范围是短暂的,这实际上会在范围结束后导致连续的可用内存。

我还怀疑,当多个任务共享同一个堆但如果手头的范围都是短暂的(例如,不要阻塞),空闲内存最终应该是连续的,这可能会出现问题。或者,我可以接受的是,只允许一个任务在堆上分配内存,如果这真的很重要,其他任务则不能。

我建议的堆分配对象用法是否有效?是否有人有另一种策略来(部分)启用动态内存分配而不会冒内存碎片的风险?

4

2 回答 2

6

过去,我们已经在紧凑的嵌入式系统上完成了各种 C++ 风格的动态内存分配。您只需要遵循一些规则并小心混合短期和长期缓冲区。首先,内存池是你的朋友——正如文章所说。

此外,对于 C++ 喜欢在配对和控制结构方面进行的所有小(<64 字节)分配,单元分配方案是必不可少的 - 不仅对于碎片控制,而且对于性能。单元分配器预先分配多个相同大小的内存单元(例如 64 字节)并将它们放在空闲堆栈上。随着内存的分配,您将它们从空闲堆栈中弹出并返回。因为所有大小都是相同的,所以您只有块大小的内部碎片。因为完成后不必加入内存,所以分配和释放是 O(1) 时间。

其他一些规则:如果您需要进行长期的动态分配,则在此之前不要进行任何短期分配。先分配大缓冲区,然后分配小缓冲区,这样内存就不会分散。另一种系统是将长期分配放在堆的后面,将短期分配放在前面。我们在这方面也取得了成功。

您还可以使用多个堆(池)来隔离不同类型的分配。如果你有一些东西在代码的一个部分创建了一大堆短期分配,而另一部分遵循不同的模式,给它们一个不同的堆。

如果仔细遵循以上所有内容,将防止或限制碎片化。另一种解决方案是使用可重定位内存分配系统,其中低优先级线程可以重新排序内存,以保持它随着时间的推移连续。我也见过几次这样做——用一点性能换取 0 长期碎片。

alloca也可以提供帮助,但是如果您不遵循内存碎片预防方法,那么您也将结束您的堆栈 - 由于这往往是嵌入式领域中更有价值的资源,这可能不是一个好主意。

于 2013-05-06T15:36:41.550 回答
1

您可能会考虑使用不太流行的alloca函数在堆栈上分配可变大小的变量。这种方式没有碎片,但如果你使用alloca大变量,你可能会遇到堆栈溢出。

编辑:来自GNU C library docs的一些关于 alloca 的信息。

于 2013-05-06T15:34:55.947 回答