Effective C++ 条款50 了解new和delete的合理替换时机
1. 替换标准库提供的operator new或operator delete通常基于以下三个理由:
1). 用来检测运行上的错误.将"new 所得内存"delete掉却不幸失败会导致内存泄露,多次对同一块"new所得内存"施行delete会导致未定义行为,如果让operator new持有一串动态分配所得地址,而operator delete将地址从中移走,就可以很容易检测出上述错误;各式各样的变成错误会导致数据"overruns"(写入点在分配区块末端之后)或"underruns"(写入点在分配区块起点之前),如果自定义一个operator news并超额分配内存,使额外空间放置特定的byte patterns(即签名,signatures),而operator delete检车上述签名是否原封不动,从而判断是否发生overrun或underrun并志记那个事实以及进行非法操作的指针.
2). 为了强化效能.标准库所提供的operator new和operator delete主要用于一般目的——它们既可被长时间程序(网页服务器,web server等)接受,也可被执行时间小于一秒的程序接受;它们必须处理一系列需求,包括大块内存,小块内存,大小混合内存;它们必须接纳各种分配形态,范围从程序存活期间的少量区块动态分配,到大数量短命对象的持续分配和归还;它们还必须考虑破碎问题,这最终会导致程序无法满足大区快把内存要求.
由于对内存管理器的要求多种多样,因此标准库所提供的operator news和operator deletes采取中庸之道.因此如果对自己程序的动态内存运行形态有深刻的了解,就会发现定制版的operator new和operator delete性能胜过缺省版本.
3). 为了收集使用上的统计数据.自行定义operator new和operator delete可以帮助收集软件内存区块大小分布,寿命分布,内存归还次序,最大动态分配量等信息.
2. 以下是一个初阶段global operator new的例子,它能够促进并协助检测"overruns"和"underruns":
struct const int signature=0xDEADBEEF; typedef unsigned char Byte; //这段代码还有若干小错误,详下 void* operator new(std::size_t size) throw(std::bad_alloc){ using namespace std; size_t realSize=size+2+sizeof(int); void* pMem=malloc(realSize); if(!pMem) throw bad_alloc(); *(static_cast<int*>(pMem))=signatrue; *(reinterpret_cast<int*>(static_cast<Byte*>(pMem)+realSize-sizeof(int)))=signature; return static_cast<Byte*>(pMem)+sizeof(int); }
"这个operator new的主要缺点在于它疏忽了身为这个特殊函数所应该具备的'坚持C++规矩'的态度.例如,条款51提到所有operator news都应该内含一个循环,反复调用某个new-handling函数,这里却没有."此外,还有一个更加微妙的主题:齐位(alignment).
"许多计算机体系结构(computer architectures)要求特定类型必须放在特定的内存地址上."例如指针的地址必须是4的倍数(four-byte aligned)或doubles的地址必须是8的倍数,如果不满足这个条件可能会导致运行期硬件异常.有些体系结构如Intel x86架构的doubles可被对其余任何边界,但如果它是8-byte齐位,其访问速度将会块许多.
因此,在定制operator new的过程中,齐位(alignment)意义重大,因为C++要求所有operator news返回的指针都有适当的对齐(取决于数据类型)."malloc就是在这样的要求下工作,所以令malloc返回一个得自malloc的指针是安全的."然而上述operator new并未提供这样的保证,它返回的是一个得自malloc且偏移一个int大小的指针,因此存在不安全性!如果客户端调用operator new来获取给一个double所用的内存同时在一部"ints为4bytes且doubles必须为8-byte齐位"的机器上运行,可能会获得一个未有适当齐位的指针,这可能会导致程序崩溃或执行速度变慢.因此写一个能优良运作的内存管理器可能并不容易.许多编译器已经在它们的内存管理函数中切换到调试状态或志记状态,许多平台已有可以替代编译器自带之内存管理器的商业产品,开放源码领域的内存管理器(如Boost库的Pool)也都可用,因而可能并不需要自己定制operator new和operator delete.
3. 总结——定制operator new和operator delete的作用:
1). 检测运行错误
2). 为了收集动态分配内存之使用统计信息
3). 为了增加分配和归还的速度
4). 为了降低缺省内存管理器所带来的额外开销
5). 为了弥补缺省分配器中的非最佳齐位(编译器自带的operator news并不保证对动态分配而得的doubles采取8-byte齐位)
6). 为了将相关对象成簇集中
7). 为了获得非传统行为