Item 50: 领会何时替换 new 和 delete 才有意义
作者:Scott Meyers
译者:fatalerror99 (iTePub's Nirvana)
发布:/
让我们先回顾一下基础。为什么有些人想要替换编译器提供的 operator new 或 operator delete 版本呢?有三个最主要的原因:
由于内存管理器的特定需求,由编译器加载的 operator news 和 operator deletes 采取了 middle-of-the-road strategy(中间路线策略)不值得大惊小怪。它们的工作对每一个人来说都说得过去,但是对谁都不是最合适的。如果你对你的程序的动态内存的应用模式有充分的理解,你可能经常发现 operator new 和 operator delete 的自定义版本胜于缺省版本。对于“胜于”,我的意思是它们运行更快——有时会有数量级的提升——而且它们需要更少的内存——最高会少于 50%。对于某些(尽管不意味着全部)应用程序,用自定义版本取代普通的 new 和 delete 是获得重大性能提升的一个简单方法。
在概念上,编写一个自定义 operator new 相当简单。例如,这是一个便于 under- 和 overruns 的检测的 global operator new 的主要部分。这里有很多小麻烦,但是我们马上就来关注一下它们。
static const int signature = 0xDEADBEEF;
typedef unsigned char Byte;
// this code has several flaws—see below
void* operator new(std::size_t size) throw(std::bad_alloc)
{
using namespace std;
size_t realSize = size + 2 * sizeof(int); // increase size of request so2
// signatures will also fit inside
void *pMem = malloc(realSize); // call malloc to get theactual
if (!pMem) throw bad_alloc(); // memory
// write signature into first and last parts of the memory
*(static_cast<int*>(pMem)) = signature;
*(reinterpret_cast<int*>(static_cast<Byte*>(pMem)+realSize-sizeof(int))) =
signature;
// return a pointer to the memory just past the first signature
return static_cast<Byte*>(pMem) + sizeof(int);
}
这个 operator new 的大多数缺陷都与它没有遵循叫这个名字的函数的 C++ 惯例有关。例如,Item 51 阐明:所有的 operator new 都应该包含一个调用 new-handling function 的循环,但是这里没有。但是,Item 51 正是专用于这样的惯例,所以我在这里忽略它们。我现在要关注一个更微妙的问题:alignment(排列对齐)。
很多计算机架构要求特定类型的数据要放置在内存中具有特定性质的地址中。例如,一种架构可能要求 pointers(指针)要出现在四的倍数的地址上(也就是说,按照四字节对齐)或者 doubles(双精度浮点型)必须出现在八的倍数的地址上(也就是说,按照八字节对齐)。不遵守这样的约束会导致 hardware exceptions at runtime(运行时硬件异常)。其它的架构可能会宽容一些,但是如果满足了排列对齐的次序会得到更好的性能。例如,在 Intel x86 架构上 doubles(双精度浮点型)可以按照任意字节分界排列,但是如果他们按照八字节对齐,访问速度会快得多。
alignment(排列对齐)在这里有重大意义,因为 C++ 要求所有的 operator news 返回适合任何数据类型的排列的指针。malloc 也工作于同样的要求下,所以,让 operator new 返回它从 malloc 得到的指针是安全的。然而,在上面的 operator news 中,我们没有返回我们从 malloc 得到的指针,我们返回的指针比我们从 malloc 得到的指针偏移了一个 int 大小。无法保证这是安全的!如果客户调用 operator new 为一个 double(或者,如果我们正在编写 operator new[],一个 doubles 的数组)申请足够的内存,而且我们正在运行一台 ints 是四个字节大小而 doubles 需要八字节对齐的机器,我们就可能返回对齐不恰当的指针。这可以导致程序崩溃。或者,它只是导致运行速度变慢。无论哪种情况,这或许都不是我们想要的。
像 alignment(排列对齐)这样的细节可以用于区分专业品质的内存管理器和那些由需要解决其它任务而心烦意乱的程序员匆匆拼凑出来的东西。编写一个几乎能工作的自定义内存管理器相当容易。编写一个工作得很好的要困难得多。作为一个一般规则,我建议你不要致力于此,除非你不得不做。
很多情况下,你并非不得不做。有些编译器提供选项开关用为它们的 memory management functions(内存管理函数)打开调试和记录的功能。快速浏览一下你的编译器的文档也许可以打消你编写 new 和 delete 的念头。在很多平台上,商用产品可以替代随编译器提供的 memory management functions(内存管理函数)。为了利用它们的增强的功能以及(或许会有的)更好的性能,你需要做的全部就是重新链接。(当然,你还必须把它们买回来。)
另一个选择是开源的内存管理器。它们可用于多种平台,所以你可以下载并试用。出自于 Boost(参见 Item 55)的 Pool library 就是一个这样的开源分配器。Pool library 提供了针对自定义内存管理能提供帮助的最通常的情况之一(大数量 small objects(小对象)的分配)进行了调谐的分配器。很多 C++ 书籍,包括本书的早期版本,展示了一个 high-performance small-object allocator(高性能小对象分配器)的代码,但是它们通常忽略了可移植性和排列对齐的考虑以及线程安全等等诸如此类的麻烦的细节。真正的库会注意用健壮得多的代码。即使你决定编写你自己的 news 和 deletes,看一下开源版本很可能会为你提供对“区分几乎起作用和真正起作用”的容易忽略的细节的洞察力。(已知 alignment(排列对齐)就是一个这样的细节,值得一提的是,TR1(参见 Item 54)包含了对已发现的类型特定的排列对齐要求的支持。)
这个 Item 的主题是了解何时替换 new 和 delete 的缺省版本(无论是基于全局的还是 per-class 的)才有意义。我们现在应该比前面更详细地总结一下时机问题。
Things to Remember
本文发布于:2024-01-28 19:11:20,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/17064402849632.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |