关于c ++:为什么要替换默认的new和delete运算符?

Why would one replace default new and delete operators?

为什么shouldwould one replace the default operator newand deletewith a custom newand deleteoperators?

这是在非常亮的C++ FAQ中重载新的和删除的继续:运算符重载。

此常见问题的后续条目是:我应该如何编写ISOC++标准一致性自定义EDCOX1、0和EDCOX1?1操作符?

注:答案是基于Scott Meyers更有效的C++的经验教训。(注:这意味着是堆栈溢出的C++FAQ的一个条目。如果你想批评在这个表单中提供一个常见问题解答的想法,那么在meta上发布的开始所有这一切的地方就是这样做的地方。这个问题的答案是在C++聊天室中进行监控的,FAQ的想法一开始就出现了,所以你的答案很可能会被那些想出这个想法的人读到。


可以尝试替换newdelete操作符,原因有很多,即:好的。要检测使用错误:

错误使用newdelete可能会导致可怕的行为和记忆不明确的野兽泄漏,这有很多种方法。每种方法的各自示例如下:在newed内存上使用多个delete,在使用new分配的内存上不调用delete。重载操作符new可以保留一个已分配地址的列表,重载操作符delete可以从列表中删除地址,这样就容易检测到此类使用错误。好的。

类似地,各种编程错误也会导致数据溢出(在分配的块结束之后写入)和欠载(在分配的块开始之前写入)。重载的操作符new可以在内存提供给客户机之前和之后,过度分配块并放置已知的字节模式("签名")。重载的运算符删除可以检查签名是否仍然完整。因此,通过检查这些签名是否完整,可以确定在分配的块的生命周期中的某个时间发生了溢出或运行不足,并且operator delete可以记录该事实以及有问题的指针的值,从而帮助提供良好的诊断信息。好的。要提高效率(速度和内存):

newdelete操作符对每个人都很好地工作,但对任何人都是最佳的。这种行为源于这样一个事实,即它们是专为一般用途而设计的。它们必须适应分配模式,从程序运行期间存在的几个块的动态分配到大量短期对象的恒定分配和释放。最终,与编译器一起装运的操作员new和操作员delete采取了中间策略。好的。

如果您对程序的动态内存使用模式有了很好的了解,您通常会发现自定义版本的operator new和operator delete优于默认版本(性能更快,或者所需内存更少,最多50%)。当然,除非你确定自己在做什么,否则这样做不是一个好主意(如果你不理解其中的复杂性,甚至不要尝试这样做)。好的。要收集使用统计信息,请执行以下操作:

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

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

所有这些信息都可以通过替换自定义的newdelete以及在过载的newdelete中添加诊断收集机制来收集。好的。为了补偿new中的次优内存对齐:

许多计算机体系结构要求将特定类型的数据放在特定类型地址的内存中。例如,体系结构可能要求指针出现在4的倍数(即四字节对齐)的地址上,或者双精度指针必须出现在8的倍数(即八字节对齐)的地址上。不遵守这些约束可能会导致运行时出现硬件异常。其他的架构更为宽泛,并且可能允许它在降低性能的情况下工作。双打的分配。在这种情况下,将默认的操作符new替换为保证八字节对齐的操作符可能会大大提高程序性能,这是替换newdelete操作符的一个很好的理由。好的。要将相关对象彼此靠近,请执行以下操作:

如果您知道特定的数据结构通常一起使用,并且您希望在处理数据时最小化页面错误的频率,那么为数据结构创建一个单独的堆,以便将它们聚集在尽可能少的页面上是有意义的。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字节)的请求,你只需要为达到这个大小的对象提供对齐的内存,因为试图存储任何更大的东西都会导致未定义的行为。好的。

在讨论了这些准备工作之后,让我们回到最初的问题,即为什么您希望重载这些运算符。首先,我应该指出,重载全局运算符的原因往往与重载特定于类的运算符的原因大不相同。好的。

因为它更常见,我将首先讨论特定于类的操作符。类特定内存管理的主要原因是性能。这通常有两种形式:要么提高速度,要么减少碎片。内存管理器只处理特定大小的块,因此它可以返回任何可用块的地址,而不是花费任何时间检查块是否足够大,如果块太大则将其拆分为两个等,从而提高了速度。碎片以相同的方式减少(大部分)例如,预分配ng一个足够容纳n个对象的块正好为n个对象提供了所需的空间;分配一个对象的内存值将精确地为一个对象分配空间,而不是多分配一个字节。好的。

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

也可以使用非默认的全局分配器来提高性能。一个典型的例子是替换一个通常速度很慢的默认分配器(例如,至少一些4.x左右的MS VC++版本会为每个分配/删除操作调用系统HeapAllocHeapFree函数)。我在实践中看到的另一种可能性是在使用SSE操作时在英特尔处理器上发生的。这些操作在128位数据上进行。尽管操作将不考虑对齐,但当数据与128位边界对齐时,速度会提高。一些编译器(例如,MS VC++again2)不一定要强制对齐到更大的边界,因此即使使用默认分配器的代码可以工作,替换分配也可以显著提高这些操作的速度。好的。

  • 大多数要求涵盖在C++标准的第3.7.3和第18.4条中(或在C++0X中的3.7.4和18.6),至少与N329 1一样。
  • 我觉得有必要指出,我并不打算选择微软的编译器——我怀疑它有很多这样的问题,但我碰巧使用了很多,所以我往往很清楚它的问题。
  • 好啊。


    Many computer architectures require that data of particular types be placed in memory at particular kinds of addresses. For example, an architecture might require that pointers occur at addresses that are a multiple of four (i.e., be four-byte aligned) or that doubles must occur at addresses that are a multiple of eight (i.e., be eight-byte aligned). Failure to follow such constraints can lead to hardware exceptions at run-time. Other architectures are more forgiving, and may allow it to work though reducing the performance.

    澄清:如果一个体系结构需要(例如)double数据是八字节对齐的,那么就没有什么可优化的了。任何一种适当规模的动态分配(如malloc(size)operator new(size)operator new[](size)new char[size],其中size >= sizeof(double)保证正确对齐。如果一个实现不能保证这一点,那么它是不一致的。在这种情况下,将operator new更改为"正确的事情",将是"修复"实现的尝试,而不是优化。

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


    The operator new that ship with some compilers don't guarantee eight-byte alignment for dynamic allocations of doubles.

    请给我引文。通常,默认的new操作符只比malloc包装稍微复杂一些,按照标准,malloc包装返回的内存适合于目标体系结构支持的任何数据类型。

    我不是说没有充分的理由为自己的类重载new和delete…你已经在这里提到了几个合法的,但上面不是其中之一。


    从我的回答中重复"有什么理由使全局新建和删除超负荷?"这里——查看那个答案(或者其他问题的答案),了解更详细的讨论、参考和其他原因。这些原因通常适用于本地运算符重载和默认/全局运算符重载,也适用于cmalloc/calloc/realloc/free重载或挂钩。

    We overload the global new and delete operators where I work for many
    reasons:

    • pooling all small allocations -- decreases overhead, decreases fragmentation, can increase performance for small-alloc-heavy apps
    • framing allocations with a known lifetime -- ignore all the frees until the very end of this period, then free all of them
      together (admittedly we do this more with local operator overloads
      than global)
    • alignment adjustment -- to cacheline boundaries, etc
    • alloc fill -- helping to expose usage of uninitialized variables
    • free fill -- helping to expose usage of previously deleted memory
    • delayed free -- increasing the effectiveness of free fill, occasionally increasing performance
    • sentinels or fenceposts -- helping to expose buffer overruns, underruns, and the occasional wild pointer
    • redirecting allocations -- to account for NUMA, special memory areas, or even to keep separate systems separate in memory (for e.g.
      embedded scripting languages or DSLs)
    • garbage collection or cleanup -- again useful for those embedded scripting languages
    • heap verification -- you can walk through the heap data structure every N allocs/frees to make sure everything looks ok
    • accounting, including leak tracking and usage snapshots/statistics (stacks, allocation ages, etc)

    我使用它来分配特定共享内存领域中的对象。(这与Russell Borogove提到的类似。)

    几年前我为洞穴开发了软件。这是一个多墙虚拟现实系统。它使用一台电脑驱动每个投影仪;最多6台(4个墙壁、地板和天花板),而3台更常见(2个墙壁和地板)。机器通过特殊的共享内存硬件进行通信。

    为了支持它,我从我的普通(非cave)场景类派生了一个新的"new",它将场景信息直接放在共享内存领域中。然后我把指针传递给不同机器上的从属渲染器。


    与使用统计相关:按子系统预算。例如,在基于控制台的游戏中,您可能需要为3D模型几何保留一些内存片段,一些用于纹理,一些用于声音,一些用于游戏脚本等。自定义分配器可以按子系统标记每个分配,并在超出单个预算时发出警告。