为了账号安全,请及时绑定邮箱和手机立即绑定

为什么要替换默认的new和Delete运算符?

为什么要替换默认的new和Delete运算符?

C++
慕虎7371278 2019-12-12 13:52:37
为什么要一个会替换默认的操作new和delete使用自定义new和delete运营商?这是在极富启发性的C ++ FAQ:运算符重载中继续Overloading new和delete的延续。该常见问题解答的后续条目是:如何编写符合ISO C ++标准的自定义new和delete运算符?注意:答案是基于Scott Meyers的《更有效的C ++》中的课程。(注意:这本来是Stack Overflow的C ++ FAQ的条目。如果您想批评以这种形式提供FAQ的想法,那么在所有这些都开始的meta上的张贴将是这样做的地方。该问题在C ++聊天室中进行监控,该问题最初是从FAQ想法开始的,所以提出这个想法的人很可能会读懂您的答案。)
查看完整描述

3 回答

?
拉丁的传说

TA贡献1789条经验 获得超8个赞

出于多种原因,人们可能会尝试替换newdelete操作符,即:

要检测使用错误:

有多种方法会导致错误使用newdelete可能导致未定义行为内存泄漏的可怕野兽。每种示例分别为:在ed内存上
使用多个内存,而不在使用分配的内存上调用。 重载的运算符可以保留分配的地址列表,而重载的运算符可以从列表中删除地址,因此很容易检测到此类使用错误。deletenewdeletenew
newdelete

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


要提高效率(速度和内存):

newdelete为大家运营商合作得相当好,但最佳的人。此行为是由于它们仅设计用于通用目的而引起的。它们必须适应各种分配模式,从在程序运行期间存在的几个块的动态分配到大量短期对象的持续分配和重新分配。最终,运算符newdelete编译器附带的运算符采取了中间策略。

如果您对程序的动态内存使用模式有很好的了解,则通常会发现自定义版本的operator new和operator delete性能优于默认版本(性能更快,或需要的内存最多减少50%)。当然,除非您确定自己在做什么,否则这样做不是一个好主意(如果您不了解所涉及的复杂性,甚至不要尝试这样做)。


收集使用情况统计信息:

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

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

所有这些信息都可以通过替换习惯newdelete在重载的new和中添加诊断收集机制来收集delete


为了补偿次优的内存对齐方式new

许多计算机体系结构要求将特定类型的数据放置在内存中特定种类的地址处。例如,体系结构可能要求指针出现在四个整数倍的地址(即四字节对齐),或者双精度必须出现在八个整数倍的地址(即八字节对齐)。不遵守这些约束条件可能会导致在运行时出现硬件异常。其他架构则更为宽容,尽管降低了性能new,但仍可能允许其工作。某些编译器附带的运算符不保证双精度字节的动态分配的八字节对齐。在这种情况下,替换默认运算符new保证八字节对齐的程序可以大大提高程序性能,并且可能是替换newdelete操作员的好理由。


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

如果您知道特定的数据结构通常一起使用,并且希望最大程度地减少处理数据时出现页面错误的频率,那么为数据结构创建一个单独的堆是有意义的,因此可以将它们聚集在很少的数量上页面。的自定义展示位置版本,new并且delete可以实现此类聚类。


要获得非常规行为:

有时,您希望操作员是新的并且要删除,以执行编译器提供的版本不提供的操作。
例如:您可能编写了一个自定义运算符delete,该运算符用零覆盖释放的内存,以提高应用程序数据的安全性。


查看完整回答
反对 回复 2019-12-12
?
回首忆惘然

TA贡献1847条经验 获得超11个赞

首先,确实有许多不同的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))并不一定要与更大的边界对齐,因此即使使用默认分配器的代码可以工作,替换分配也可以大大提高这些操作的速度。


  1. C ++标准的第3.7.3节和第18.4节(或C ++ 0x的第3.7.4节和第18.6节,至少从N3291开始)涵盖了大多数要求。

  2. 我感到有必要指出,我不打算选择Microsoft的编译器-我怀疑它会出现异常数量的此类问题,但是我经常使用它,因此我倾向于非常了解它的问题。


查看完整回答
反对 回复 2019-12-12
?
慕尼黑的夜晚无繁华

TA贡献1864条经验 获得超6个赞

许多计算机体系结构要求将特定类型的数据放置在内存中特定种类的地址处。例如,体系结构可能要求指针出现在四个整数倍的地址(即四字节对齐),或者双精度必须出现在八个整数倍的地址(即八字节对齐)。不遵守这些约束条件可能会导致在运行时出现硬件异常。其他体系结构则更为宽容,尽管降低了性能,也可能使其工作。


需要说明的是:如果某个架构要求例如将double数据按8字节对齐,则没有什么要优化的。任何种类的适当大小的动态分配的(例如malloc(size),operator new(size),operator new[](size),new char[size]其中size >= sizeof(double))是保证适当对准。如果一个实现不能保证这一点,那就不符合要求。operator new在这种情况下,更改为执行“正确的事情”将是尝试“修复”实现,而不是优化。


另一方面,某些体系结构允许一种或多种数据类型的不同(或所有)对齐方式,但根据那些相同类型的对齐方式,提供不同的性能保证。然后,实现可以返回次优对齐且仍然符合要求的内存(再次假设为适当大小的请求)。这就是该示例的含义。


查看完整回答
反对 回复 2019-12-12
  • 3 回答
  • 0 关注
  • 595 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信