为何需要自定义opertaor new与operator delete?
有三个理由:
- 检测运用上的错误
如果new所得的内存delete失败,会导致内存泄漏。如果试图多次delete,则又会导致未定义行为。针对该误操作的解决方法是:令operator new持有一串动态分配所得地址,delete将地址从中移除。
各式各样的编程错误可能会导致数据overruns(写入点在分配区段尾部之后)或者underruns(起点之前)。其解决方法是:自定义operator new,分配超额内存,以额外空间放置特定的byte patterns(signature)operator delete执行时先检查这段签名是否原封不动,若否则表明出现了overrun或者underrun,log此事实以及这个指针。 - 强化效能
编译器所带的operator new与operator delete总是惯于采取通用解(不针对特殊情况使用最优解),如果你针对自己的程序作出特定优化(内存大小,分配形态,持续分配与归还),性能将会得到极大的提高。 - 统计使用数据
在自定义内存管理服务前我们当然需要采集目前程序对内存的使用情况,我们可以通过自定义operator new与operator delete来搜集内存使用信息。
自定义operator new实例
以下是一个帮助检查overruns或underruns的operator new实例,其中存在着一些小错误:1
2
3
4
5
6
7
8
9
10
11
12
13static const int signature = 0xDEADBEEF;//签名
typedef unsigned char Byte;
//存在一些小错误
void* operator new(size) throw(bad_alloc){
using namespace::std;
size_t realSize = size+2*sizeof(int);//实际需要额外塞入两个signature
void* pMem = malloc(realSize);
if(!pMem) throw bad_alloc();
*(static_cast<int*>(pMem) = signature;
*(reinterpret_cast<int*>(static_cast<Byte*>(pMem)+realSize-sizeof(int))) = signature;
return static_cast<Byte*>(pMem) +sizeof(int);
}
其缺点主要在于operator new应该内含某一个循环,反复调用某个new-handler函数。这个问题会在下一节具体说明,本节针对该实例讨论重点是齐位(alignment)。
齐位
许多计算机体系结构会要求特定类型存放于特定的地址,比如指针的地址必须是4倍数或者double的地址必须是8倍数。有的体系不遵循该规则会导致硬件异常,有的体系则无此要求,只是如果遵循齐位则提供较佳焦虑。
在上一个实例中,malloc返回的指针必然是齐位的,但是自定义的operator new并没有返回得自malloc的指针,而是做了一些偏移,这可能会造成错误或效率低下。事实上,各个库的内存管理函数或者部分开源内存管理器均考虑了齐位特性。如果我们也有自定义内存管理的需求,记得考虑诸如可移植性,齐位,线程安全等等细节。
何时需要自定义opertaor new与operator delete
- 需要检测运用错误(如前所述)
- 需要收集使用信息(如前所述)
- 需要增加分配与归还的速度
举例而言,如果在profile时发现线程安全性降低了运行速度,而我们的程序只在单线程下运行,则没必要使用线程安全的operator new。 - 需要降低默认内存管理器带来的空间额外开销
- 弥补默认分配器带来的非最佳齐位
- 需要将对象成簇集中
如果每个数据结构往往被一起使用,而你有希望使用它们时page faults的频率降至最低,那么为此创建一个heap就很有必要。new与delete的placement版本可以完成这样的行为。 - 需要获得非传统行为
例如分配和归还共享内存。