一. 内容
-
怎么会有人想要替换编译器提供的 operator new 或 operator delete 呢?以下是七个常见的理由:
-
用来
检测运用上的错误
。当将 new 所得内存 delete 失败时,会导致内存泄漏。当在 new 所得内存 多次 delete 时,会导致未定义行为。如果自定义 new 或者 delete,就很容易检测出这类错误。此外,各式各样的编程错误,可能导致数据越界,超出已分配的内存,包括上溢(over-runs)和下溢(under-runs)。如果使用自定义的 new,就可以额外分配空间用于放置溢出标签,再使用自定义 delete 检测是否溢出。这样就可以及时检测越界错误。
-
为了收集使用上的
统计数据
。你可能需要了解你的软件是如何使用动态内存,来评估和改善性能。
常见的内存问题包括:分配区块大小分布如何?寿命分布如何?它们倾向于以 FIFO 次序或 LIFO 次序或随机次序来分配和归还?它们是否随着执行阶段不同存在不同的分配和归还形态?任何时刻所使用的最大动态分配量是多少?
自定义的 operator new 和 operator delete 使得我们轻易收集到这些信息。
-
为了增加
分配和归还速度
。泛用型分配器往往比定制型分配器慢,特别是当定制型分配器专门针对某特定类型设计时。比如你的程序是单线程,但你的编译器提供的内存分配器具备线程安全,那你或许可以写个不具线程安全的分配器来大幅改善速度。当然,首先请分析你的程序瓶颈确实发生在 new 和 delete 身上。
-
为了降低缺省内存管理器带来的
额外空间开销
。泛用性分配器往往还使用更多的内存,因为它常常在每一个分配区块上使用更多信息。
-
为了弥补缺省分配器的非最佳
齐位
。许多计算机体系结构要求特定的类型必须放在特定的内存地址上。例如它可能要求指针的地址必须是 4 倍数,double 的地址必须是 8 倍数。如果没有这个约束条件,可能导致运行期硬件异常。有些体系必须仁慈,而是宣称如果齐位,便提供较佳效率。
-
为了将相关对象
成簇
集中。如果你知道某个特定数据对象往往一起使用,而你又希望在处理这些数据时将缺页频率降到最低。那么为此数据对象创建另一个 heap 就有意义。 new 和 delete 的 placement 版本就有可能完成这样的行为,见条款52。
-
为了获得
非传统行为
。有时候你希望 operator new 和 operator delete 做些编译器未做的事情。例如使用 C 的 API,或者 delete 的内存全部赋予 0 值。
-
-
但是也有很多情况下是没必要的,至少不需要自己写:
- 某些编译器已经具有
内存调试(debugging)和 志记(logging)
。这很有可能消除自行撰写的需要。许多平台也有商业化的产品,如果有需要可以花点钱买下它们,然后重新链接程序。 - 开放源码领域的内存管理器。有很多开源的内存管理器项目,比如 Boost 程序库的 Pool 就是这样一个,许多 C++ 书籍也曾展示高效率的小型分配器源码。如果执意要写一个自己版本的 new 和 delete,那么这些开源的项目关于如何
保证可移值性,齐位,线程安全,异常安全...细节
,都值得借鉴。
- 某些编译器已经具有
二. 总结
- 有许多理由需要写个自定的 new 和 delete,包括改善性能,对 heap 运用错误进行调用,收集 heap 使用信息。