陈硕 (giantchen_AT_gmail)
Blog.youkuaiyun.com/Solstice
本文只考虑 Linux x86 平台,服务端开发(不考虑 Windows 的跨 DLL 内存分配释放问题)。本文假定读者知道 ::operator new() 和 ::operator delete() 是干什么的,与通常用的 new/delete 表达式有和区别和联系,这方面的知识可参考侯捷先生的文章《池内春秋》[1] ,或者这篇文章 。
C++ 的内存管理是个老生常谈的话题,我在《当析构函数遇到多线程 》第 7 节“插曲:系统地避免各种指针错误”中简单回顾了一些常见的问题以及在现代 C++ 中的解决办法。基本上,按现代 C++ 的手法(RAII)来管理内存,你很难遇到什么内存方面的错误。“没有错误”是基本要求,不代表“足够好”。我们常常会设法优化性能,如果 profiling 表明 hot spot 在内存分配和释放上,重载全局的 ::operator new() 和 ::operator delete() 似乎是一个一劳永逸好办法(以下简写为“重载 ::operator new()”),本文试图说明这个办法往往行不通。
内存管理的基本要求
如果只考虑分配和释放,内存管理基本要求是“不重不漏”:既不重复 delete,也不漏掉 delete。也就说我们常说的 new/delete 要配对,“配对”不仅是个数相等,还隐含了 new 和 delete 的调用本身要匹配,不要“东家借的东西西家还”。例如:
- 用系统默认的 malloc() 分配的内存要交给系统默认的 free() 去释放;
- 用系统默认的 new 表达式创建的对象要交给系统默认的 delete 表达式去析构并释放;
- 用系统默认的 new[] 表达式创建的对象要交给系统默认的 delete[] 表达式去析构并释放;
- 用系统默认的 ::operator new() 分配的的内存要交给系统默认的 ::operator delete() 去释放;
- 用 placement new 创建的对象要用 placement delete (为了表述方便,姑且这么说吧)去析构(其实就是直接调用析构函数);
- 从某个内存池 A 分配的内存要还给这个内存池。
- 如果定制 new/delete,那么要按规矩来。见 Effective C++ 相关条款。
做到以上这些不难,是每个 C++ 开发人员的基本功。不过,如果你想重载全局的 ::operator new(),事情就麻烦了。
重载 ::operator new() 的理由
Effective C++ 第三版第 50 条列举了定制 new/delete 的几点理由:
- 检测代码中的内存错误
- 优化性能
- 获得内存使用的统计数据
这些都是正当的需求,文末我们将会看到,不重载 ::operator new() 也能达到同样的目的。
::operator new() 的两种重载方式
1. 不改变其签名,无缝直接替换系统原有的版本,例如:
#include