Item 50:为什么需要自定义 new 和 delete?

为什么需要自定义 operator new 或 operator delete

  • 检测使用错误。new 得到的内存如果没有 delete 会导致内存泄露,而多次 delete 又会引发未定义行为。如果自定义 operator new 来保存动态内存的地址列表,在 delete 中判断内存是否完整,便可以识别使用错误,避免程序崩溃的同时还可以记录这些错误使用的日志。
  • 提高效率。全局的 new 和 delete 被设计为通用目的(general purpose)的使用方式,通过提供自定义的new,我们可以手动维护更适合应用场景的存储策略。
  • 收集使用信息。在继续自定义 new 之前,你可能需要先自定义一个 new 来收集地址分配信息,比如动态内存块大小是怎样分布的?分配和回收是先进先出 FIFO 还是后进先出 LIFO?
  • 实现非常规的行为。比如考虑到安全,operator new 把新申请的内存全部初始化为0。
  • 其他原因,比如抵消平台相关的字节对齐,将相关的对象放在一起等等。

自定义 operator new

自定义一个 operator new 很容易的,比如实现一个支持越界检查的 new:

static const int signature = 0xDEADBEEF;    // 边界符
typedef unsigned char Byte; 
 
void* operator new(std::size_t size) throw(std::bad_alloc) {
    // 多申请一些内存来存放占位符 
    size_t realSize = size + 2 * sizeof(int); 
 
    // 申请内存
    void *pMem = malloc(realSize);
    if (!pMem) throw bad_alloc(); 
 
    // 写入边界符
    *(reinterpret_cast<int*>(static_cast<Byte*>(pMem)+realSize-sizeof(int))) 
        = *(static_cast<int*>(pMem)) = signature;
 
    // 返回真正的内存区域
    return static_cast<Byte*>(pMem) + sizeof(int);
}

其实上述代码是有一些瑕疵的:

  • operator new 应当不断地调用new handler,上述代码中没有遵循这个惯例;
  • 有些体系结构下,不同的类型被要求放在对应的内存位置。比如 double 的起始地址应当是 8 的整数倍,int的起始地址应当是 4 的整数倍。上述代码可能会引起运行时硬件错误。
  • 起始地址对齐。C++要求动态内存的起始地址对所有类型都是字节对齐的,new 和 malloc 都遵循这一点,然而我们返回的地址偏移了一个int。

到此为止你已经看到了,实现一个 operator new 很容易,但实现一个好的 operator new 却很难。

总结

  • 有很多正当的编写 new 和 delete 的自定义版本的理由,包括改进性能,调试 heap(堆)用法错误,以及收集 heap(堆)用法信息。
posted @ 2020-02-29 11:36  刘-皇叔  阅读(160)  评论(0编辑  收藏  举报