为什么要替换默认的新运算符和删除运算符?

为什么人会替换默认的运营商newdelete一个自定义newdelete运营商?

这是在极其明亮的C ++ FAQ中重载新的和删除的继续:
运算符超载。

此常见问题解答的后续条目是:
我应该如何编写符合ISO C ++标准的自定义newdelete操作符?

注意:答案是基于Scott Meyers的More Effective C ++的经验教训。
(注意:这是一个Stack Overflow的C ++常见问题解答的入口,如果你想批评在这个表单中提供常见问题的想法,那么开始所有这些的meta上的贴子将成为这样做的地方。那个问题在C ++聊天室中进行监控,常见问题的想法首先出现在C ++聊天室中,所以你的答案很可能会被那些提出这个想法的人阅读。)


人们可能会尝试更换newdelete操作员,原因有很多,即:

检测使用错误:

有许多方法可能会导致不确定的行为和内存泄漏,导致newdelete错误使用。 各自的示例是:
new编辑的内存上使用多个delete ,并且不会调用使用new分配的内存上的delete操作。
重载的operator new可以保存分配的地址列表,并且重载的operator delete可以从列表中删除地址,然后很容易检测到这种使用错误。

类似地,各种编程错误可能会导致数据溢出 (超出分配块的末尾写入)和欠载 (在分配块开始之前写入)。
重载操作符new可以在客户端可用的内存之前和之后过度分配块并放置已知的字节模式(“签名”)。 重载的操作符删除可以检查签名是否完好无损。 因此,通过检查这些签名是否不完整,可以确定在分配块的生命周期中某个时间发生了溢出或溢出,并且操作员删除可以记录该事实以及有问题的指针的值,从而帮助提供良好的诊断信息。


提高效率(速度和记忆):

newdelete操作员对每个人都合理地工作,但对于任何人都是最佳的。 这种行为来自于它们仅为通用目的而设计的事实。 他们必须适应分配模式,从计划期间存在的几个块的动态分配到大量短期对象的常量分配和重新分配。 最终,编译器附带的操作员new操作符和操作员delete操作delete采取中间策略。

如果你对程序的动态内存使用模式有了很好的理解,你可以经常发现operator new和operator delete的自定义版本比默认版本性能更好(性能更快,或者需要更少的内存,高达50%)。 当然,除非你确定自己在做什么,否则这不是一个好主意(如果你不了解错综复杂的情况,甚至不要尝试这样做)。


收集使用统计信息:

在考虑如#2中提到的为了提高效率而更换newdelete之前,您应该收集有关应用程序/程序如何使用动态分配的信息。 您可能想收集有关以下内容的信息:
分配块的分配,
生命时间的分配,
分配顺序(FIFO或LIFO或随机),
了解使用模式在一段时间内的变化,使用的最大动态内存量等。

另外,有时您可能需要收集使用信息,例如:
计算一个类的动态对象的数量,
限制使用动态分配等创建的对象数量

所有这些信息都可以通过替换自定义的newdelete添加,并在重载的newdelete添加诊断收集机制。


为了补偿new理想的内存对齐:

许多计算机体系结构要求将特定类型的数据放置在特定类型的地址的内存中。 例如,体系结构可能要求指针出现在4的倍数(即,四字节对齐)或双倍必须出现在8的倍数(即,八字节对齐)的地址处。 如果不遵循这些约束,可能会导致运行时出现硬件异常。 其他体系结构更为宽容,并可能允许它通过降低性能来工作。运行时带有一些编译器的new运算符不保证动态分配双精度的八字节对齐方式。 在这种情况下,将默认操作符new替换为保证八字节对齐的默认操作符可以大大提高程序性能,并且可以成为替换new操作符和delete操作符的很好理由。


要将相关对象聚集在一起:

如果您知道特定的数据结构通常一起使用,并且希望在处理数据时最大限度地减少页面错误的频率,那么为数据结构创建单独的堆可能很有意义,以便它们尽可能少地聚集在一起页面尽可能。 自定义Placement版本的newdelete可以实现此类群集。


获得非常规行为:

有时你希望操作符new和delete做一些编译器提供的版本不提供的功能。
例如:您可以编写自定义运算符delete ,以便用零覆盖释放的内存以提高应用程序数据的安全性。


首先,真的有很多不同的newdelete操作符(真的是一个任意数字)。

首先,有::operator new::operator new[]::operator delete::operator delete[] 。 其次,对于任何类X ,都有X::operator newX::operator new[]X::operator deleteX::operator delete[]

在这些之间,比全局操作符重载特定于类的操作符更为常见 - 特定类的内存使用遵循足够特定的模式,您可以编写提供对默认值进行实质性改进的操作符,这是相当常见的。 在全球范围内精确或专门预测内存使用情况通常要困难得多。

可能还值得一提的是,虽然operator newoperator new[]是相互独立的(对于任何X::operator newX::operator new[] )也是如此,两者的要求没有区别。 一个将被调用来分配一个对象,另一个分配一个对象数组,但是每个对象仍然只是接收需要的大量内存,并且需要返回一块内存块的地址(至少)。

说到需求,回顾其他需求可能是值得的:1:全球运营商必须是真正的全球运营商 - 您不可以在名称空间内放置一个名称空间,也不要在特定翻译单位中放置一个名称空间。 换句话说,只有两个级别可以发生重载:特定于类的重载或全局重载。 不允许在诸如“命名空间X中的所有类”或“翻译单元Y中的所有分配”之间的中间点。 具体类的经营者必须是static -但你实际上并不需要声明为静态的-它们是静态的你是否明确声明他们static或没有。 正式的,全球运营商很多人都将内存对齐,以便它可以用于任何类型的对象。 非正式地说,在一个方面有一点摆动空间:如果你得到一个小块的请求(例如,2个字节),你只需要为一个尺寸最大的对象提供内存对齐,因为试图存储更大的东西无论如何都会导致未定义的行为。

在介绍了这些预备知识之后,让我们回到最初的问题,即为什么您想要让这些运营商超载。 首先,我应该指出,全球运营商超载的原因往往与特定类别运营商超载的原因大不相同。

由于它更常见,我会首先讨论特定类的操作符。 特定于类的内存管理的主要原因是性能。 这通常以两种形式(或两种形式)出现:提高速度或减少碎片。 速度由于内存管理器仅处理特定大小的块而得到改善,所以它可以返回任何空闲块的地址,而不是花时间检查块是否足够大,如果块是分块(大部分)都会以相同的方式减少碎片 - 例如,预先分配足够大的N个对象的块会提供N个对象所需的空间; 分配一个对象的内存值将精确地分配一个对象的空间,而不是多个单独的字节。

全局内存管理操作员负担过重的原因有很多种。 其中许多是面向调试或检测的,例如跟踪应用程序所需的全部内存(例如,准备移植到嵌入式系统),或者通过显示分配和释放内存之间的不匹配来调试内存问题。 另一种常见策略是在每个请求块的边界之前和之后分配额外的内存,并在这些区域中写入独特的模式。 在执行结束时(也可能在其他时间),检查这些区域以查看代码是否已写入分配的边界之外。 另一种方法是尝试通过自动化至少某些内存分配或删除方面来提高易用性,例如使用自动垃圾回收器。

非默认的全局分配器也可以用来提高性能。 一个典型的例子就是替换一个通常很慢的默认分配器(例如,至少某些4.x版本的MS VC ++版本会为每个分配/删除操作调用系统HeapAllocHeapFree函数)。 我在实践中看到的另一种可能性是在使用SSE操作时发生在Intel处理器上。 这些操作在128位数据上。 虽然这些操作不管对齐如何都能正常工作,但是当数据与128位边界对齐时,速度会得到改善。 一些编译器(例如MS VC ++再次2)不一定强制对齐到更大的边界,因此即使使用默认分配器的代码可以工作,替换分配也可以为这些操作提供显着的速度改进。


  • C ++标准的§3.7.3和§18.4(或C ++ 0x中的§3.7.4和§18.6,至少在N3291中)涵盖了大部分要求。
  • 我感到有必要指出,我不打算选择微软的编译器 - 我怀疑它有这么多问题,但我碰巧使用了很多,所以我倾向于意识到它的问题。

  • 许多计算机体系结构要求将特定类型的数据放置在特定类型的地址的内存中。 例如,体系结构可能要求指针出现在4的倍数(即,四字节对齐)或双倍必须出现在8的倍数(即,八字节对齐)的地址处。 如果不遵循这些约束,可能会导致运行时出现硬件异常。 其他体系结构更为宽容,并可能允许它通过降低性能来工作。

    为了阐明:如果一个架构要求例如double数据是八字节对齐的,那么没有什么可以优化的。 保证适当大小的任何类型的动态分配(例如malloc(size)operator new(size)operator new[](size)new char[size] ,其中size >= sizeof(double) ) 。 如果一个实现没有做出这个保证,那么它不符合。 在这种情况下,改变operator new以做“正确的事情”将会是“修复”实施的尝试,而不是优化。

    另一方面,一些体系结构允许对一种或多种数据类型进行不同(或全部)对齐,但根据这些相同类型的对齐情况提供不同的性能保证。 然后,一个实现可能会返回内存(同样,假设请求的大小合适),这个内存是次优对齐的,并且仍然符合要求。 这就是这个例子。

    链接地址: http://www.djcxy.com/p/12681.html

    上一篇: Why would one replace default new and delete operators?

    下一篇: What is an example of a difference in allowed usage or behavior between an xvalue and a prvalue FOR NON