除非您正在对操作系统或嵌入式系统的某些部分进行编程,否则有什么理由这样做吗?我可以想象,对于某些频繁创建和销毁的特定类,重载内存管理功能或引入对象池可能会降低开销,但是全局执行这些操作?
另外
,我刚刚在重载的删除函数中发现了一个错误——内存并不总是被释放。那是在内存不那么关键的应用程序中。此外,禁用这些重载只会降低约 0.5% 的性能。
除非您正在对操作系统或嵌入式系统的某些部分进行编程,否则有什么理由这样做吗?我可以想象,对于某些频繁创建和销毁的特定类,重载内存管理功能或引入对象池可能会降低开销,但是全局执行这些操作?
另外
,我刚刚在重载的删除函数中发现了一个错误——内存并不总是被释放。那是在内存不那么关键的应用程序中。此外,禁用这些重载只会降低约 0.5% 的性能。
我们重载了我工作的全局 new 和 delete 运算符,原因有很多:
新/删除记帐的想法非常灵活和强大:例如,您可以在分配发生时记录活动线程的整个调用堆栈,并汇总有关此的统计信息。如果您出于某种原因没有空间将其保存在本地,则可以通过网络发送堆栈信息。您可以在此处收集的信息类型仅受您的想象力(当然还有性能)的限制。
我们使用全局重载是因为可以方便地在此处挂起许多常见的调试功能,并根据我们从这些相同重载中收集的统计数据对整个应用程序进行全面改进。
我们仍然对个别类型使用自定义分配器;在许多情况下,通过为 STL 数据结构的单点使用提供自定义分配器可以获得的加速或功能远远超过从全局重载中获得的一般加速。
看看一些用于 C/C++ 的分配器和调试系统,你会很快想到这些和其他想法:
(一本古老但具有影响力的书是《Writing Solid Code》,其中讨论了您可能希望在 C 中提供自定义分配器的许多原因,其中大部分仍然非常相关。)
显然,如果您可以使用这些优秀工具中的任何一个,您会想要这样做,而不是自己动手。
在某些情况下,它更快、更容易、更少的业务/法律麻烦、您的平台还没有可用的东西,或者只是更有启发性:深入研究并编写一个全局重载。
除了这里提到的其他重要用途(如内存标记)之外,它也是强制应用程序中的所有分配进行固定块分配的唯一方法,这对性能和碎片有巨大影响。
例如,您可能有一系列具有固定块大小的内存池。覆盖全局new
允许您将所有 61 字节分配定向到具有 64 字节块的池,所有 768-1024 字节分配到 1024b 块池,所有高于 2048 字节块池的分配,以及任何更大的比一般衣衫褴褛的堆要大 8kb。
因为固定块分配器比从堆中随意分配要快得多并且更不容易产生碎片,这让您甚至可以强制从池中分配蹩脚的 3d 方代码,而不是在整个地址空间中大便。
这通常在时间和空间关键的系统中完成,例如游戏。280Z28、Meeh 和 Dan Olson 描述了原因。
UnrealEngine3 重载全局 new 和 delete 作为其核心内存管理系统的一部分。有多个分配器提供不同的功能(分析、性能等),它们需要所有分配来通过它。
编辑:对于我自己的代码,我只会将其作为最后的手段。我的意思是我几乎肯定不会使用它。但是我的个人项目显然要小得多/非常不同的要求。
一些实时系统重载它们以避免在初始化后使用它们。
重载 new & delete 可以为您的内存分配添加标签。我标记每个系统或控件或中间件的分配。我可以在运行时查看每个使用了多少。也许我想看看与 UI 分离的解析器的使用情况,或者一个中间件真正使用了多少!
您还可以使用它在分配的内存周围放置保护带。如果/当您的应用程序崩溃时,您可以查看地址。如果您看到内容为“0xABCDABCD”(或任何您选择作为保护的内容),那么您正在访问您不拥有的内存。
也许在调用 delete 之后,您可以用类似可识别的模式填充这个空间。我相信 VisualStudio 在调试中会做类似的事情。它不会用 0xCDCDCDCD 填充未初始化的内存吗?
最后,如果您有碎片问题,您可以使用它重定向到块分配器吗?我不确定这真的是一个问题的频率。
当对 new 和 delete 的调用在您的环境中不起作用时,您需要重载它们。
例如,在内核编程中,默认的 new 和 delete 不起作用,因为它们依赖于用户模式库来分配内存。
从实际的角度来看,在系统库级别覆盖 malloc 可能会更好,因为 operator new 可能无论如何都会调用它。
在 linux 上,您可以使用自己的 malloc 版本代替系统版本,如下例所示:
http://developers.sun.com/solaris/articles/lib_interposers.html
在那篇文章中,他们试图收集性能统计数据。但是,如果您还覆盖 free,您也可能会检测到内存泄漏。
由于您在使用 LD_PRELOAD 的共享库中执行此操作,因此您甚至不需要重新编译您的应用程序。
我已经看到它在一个系统中完成,出于“安全”*
原因,需要覆盖它在取消分配时使用的所有内存。该方法是在每个内存块的开头分配额外的几个字节,这些字节将包含整个块的大小,然后在删除时用零覆盖。
正如您可能想象的那样,这有许多问题,但它确实(大部分)有效并且使团队免于审查相当大的现有应用程序中的每一个内存分配。
当然不是说它是一个很好的用途,但它可能是那里最具想象力的用途之一......
*
可悲的是,与其说是实际安全,不如说是安全的外观......
用 C++ 编写的 Photoshop 插件应覆盖operator new
,以便它们通过 Photoshop 获取内存。
我已经使用内存映射文件完成了它,以便写入内存的数据也会自动保存到磁盘。
如果您有内存映射的 IO 设备,或者有时您需要分配某个连续内存块,它还用于返回特定物理地址的内存。
但在 99% 的情况下,它是作为调试功能完成的,用于记录分配和释放内存的频率、地点和时间。
实际上,游戏从系统分配一大块内存然后通过重载的 new 和 delete 提供自定义分配器是很常见的。一个重要原因是控制台具有固定的内存大小,这使得泄漏和碎片成为大问题。
通常(至少在封闭平台上)默认堆操作缺乏控制和内省。对于许多应用程序来说,这并不重要,但对于在固定内存情况下稳定运行的游戏来说,增加的控制和自省都非常重要。
如果您的应用程序能够通过随机崩溃以外的其他方式响应低内存条件,这可能是一个不错的技巧。要做到这一点,您new
可以成为默认值的简单代理new
,捕捉其失败,释放一些东西并再次尝试。
最简单的技术是在启动时为此目的保留一块空白内存块。您可能还可以利用一些缓存 - 想法是一样的。
当第一次分配失败开始时,您仍然有时间警告您的用户内存不足的情况(“我将能够生存一段时间,但您可能想要保存您的工作并关闭一些其他应用程序”),将你的状态保存到磁盘,切换到生存模式,或者在你的上下文中任何其他有意义的东西。
最常见的用例可能是泄漏检查。
另一个用例是当您对环境中的内存分配有特定要求时,您正在使用的标准库不能满足这些要求,例如,您需要保证内存分配在多线程环境中是无锁的。
正如许多人已经说过的,这通常是在性能关键的应用程序中完成的,或者能够控制内存对齐或跟踪您的内存。游戏经常使用自定义内存管理器,尤其是在针对特定平台/控制台时。
这是一篇相当不错的博客文章,介绍了这样做的一种方法和一些推理。
重载的 new 运算符还使程序员能够从他们的程序中挤出一些额外的性能。例如,在一个类中,为了加快新节点的分配,维护了一个已删除节点的列表,以便在分配新节点时可以重用它们的内存。这种情况下,重载的删除操作符会将节点添加到列表中删除节点的数量和重载的 new 运算符将从该列表中分配内存,而不是从堆中分配内存,以加快内存分配。当删除的节点列表为空时,可以使用堆中的内存。