“Shit!又死机了。我已经在这平台上工作了一个月了。可死机的次数比我在这个月里被女孩甩的次数还多。为什么?还不是这该死的平台,为什么掌上系统的内存就和愿意和我说话的女孩一样少?”Solmyr抱怨道。
“兄弟,怎么了。”Zero问。
“Zero是我们这组的主程序员,他懂得很多,人长的也帅,很讨女孩子的欢心。甚至连美工组的Lili(长得比孙燕姿还好看)也暗恋他。”。Solmyr一边想,一边说,“老大,你看,又死机了。为什么我每次用动态内存超过10次,就crash了呢?”
“我知道你原来是Java程序员,也许到现在,你还念念不忘那GC (垃圾收集机制,我说还不如叫上帝也哭泣-God Cry),可你要知道,你现在是在用C++编程,重要的是效率。”
“效率,我……”忽然,Solmyr觉得嘴角边似乎有液体流过。那是Solmyr的口水。每次提到效率,Solmyr总要流口水,就像看到漂亮MM,Solmyr要喷鼻血一样。
“C++中,关于动态内存的是new and delete。”
“我知道,”Solmyr急于表现自己,想证明自己对C++并不是一无所知,“我正在读Scott Meyer的More Effective C++。在C++中,new operator是C++内建的行为。任何人(也许除了Bjarne Stroustrup)都无法改变。new operator先调用一个名为operator new的函数动态申请内存。标准模式就像这样:
void* operator new(size_t size);
然后在传回的void*指针上进行构造的行为。而delete operator则先析构对象,然后调用名为operator delete的函数,标准形式就像这样:
void operator delete(void* pToDeAlloc)
// GotW中说即使在指针参数后加上size_t size,仍然是标准形式。
// size_t size的作用是检查所要卸除的内存是否是期望的大小,如果在类层次中定义的话,只要基类是virtual destructor,那么size可以// 确保是正确大小。
而placement new……。”
“唔,说得不错,有进步。关于placement new/delete,先知Meyer[1]有详细的论述。原来的placement new仅仅是这样:
void* operator new(size_t, void* pMem) { return pMem; }
‘ 随着时间过去,任何‘要求额外引数’的 operator new 版本,也都渐渐采用 placement new 这个术语。事实上这个术语已经被铭记於 C++ 标准规格中。因此,当 C++ 程序员谈到所谓的 placement new 函数,他们所谈的可能是上述那个‘需要额外一个 void* 参数,用以指出物件置于何处’的版本,但也可能是指那些‘所需参数比单一而必要之 size_t 引数更多’的任何 operator new 版本,包括上述函数,也包括其他‘参数更多’的 operator new 函数。’——引自[1]。
现在我们来考虑一个问题,如果在operator new结束申请内存后,构造函数抛出了异常,那么已经申请的内存谁来回收,答案当然是编译器。因为整个new operator还未结束。所以你无法获得控制权。如果你对operator new/delete进行了重载,那么编译器会调用那个operator delete呢?由于不同的operator new可能通过不同的方法获得内存,而让不知道怎样分配的operator delete去释放内存显然是不负责任的。所以编译器假定哪个operator delete与operator new有相同的参数(除了size_t size和void* pDeAlloc),那么那个operator delete便知道operator new怎样获得内存。构造函数抛出异常后,也会调用与operator new相同参数的operator delete来释放内存。”
“那么,我该怎么解决现在的问题呢?”
“别那么急,已经下午五点了。该回家了,别让人以为程序员是夜游神。回家看看Effective C++第二部分和GotW9, GotW10。明天再说吧。”
第二天一早,Solmyr啃着大饼走进了办公室,看到Guru Zero早已姿势优雅地坐在电脑前收发Email,不禁自惭形秽,连忙放下手中的大饼,跑去和Zero说:“老大,昨天晚上,我挑灯夜读,总结出两点:
1. 如果你写了一个operator new,请对应写一个operator delete。
2. 不要delete不是自己new来的内存。
“嗯,不错,怎么我看上去,就象是Effective C++中的条款呢?你有没有自己想过关于placement delete的问题?”
“placement delete有什么问题吗?”Solmyr一脸茫然的问道。
“你有没有试过把一块用placement new申请得到的内存用placement delete卸除掉呢?不妨你现在试试看。”
只见Solmyr跑到一台电脑前,两手如飞在键盘上敲击,可是我们能听到的只有他的唉声叹气和编译器的哇哇大叫。Solmyr实在是没办法,只能向Zero求教。Zero喝了一口咖啡,说道:“我们平时写的那些要求额外参数的operator delete只有在构造函数抛出异常时,才会被编译器自动调用,而我们是不可能手工调用到任何带有额外参数的delete的,为什么没有一个内建的‘placement delete’来与‘placement new’相匹配的原因在于没有办法保证它被正确使用,在C++类型系统中,无法推断一个指针从哪里获得它指向的内存,可能是指向heap,也可能是stack。所以……”
“所以当我们确实知道一个指针它是怎么获得它所指向的内存时,我们可以这样:
template<class T> void destroy(T* p, Arena& a)
{
if (p) {
p->~T(); // explicit destructor call
a.deallocate(p);
}
}
这样,就不会有资源泄漏了。”[2]
“可是我怎么解决我那稀有的内存的问题呢?”
“标准库中的allocator和boost中的pool可以解决内存管理的问题,不必每次调用operator new,从而少了一些开销,不过早上我刚收到客户的mail,他们说准备在他们的设备上再加12MB的内存。”
“……”Solmyr郁闷中。
[1]Object Counting in C++ CUJ 1998.4 中文译文 陈崴 http://www.jjhou.com/(程序员杂志:2001.8 杂志上少了placement new and placement delete)
[2] Bjarne Stroustrup's C++ Style and Technique FAQ Is there a "placement delete"?