讲两件事情:第一件是我们必须做到的,不然OOM来袭会造成程序崩溃,第二件是我们最好做到的,不然OOM来袭我们不能彻底恢复。
1。防止OOM恢复过程中的crash
假设一个类有如下初始化和清除代码:
void CMyClass::Init()
{
m_pPointer = new CMyOtherClass;
}
void CMyClass::Destroy()
{
delete m_pPointer;
}
如果new CMyOtherClass的过程中,内存耗尽,这个时候系统会直接跳转到异常处理代码,一般来说异常处理代码会清除掉当前所有context,重新来过。这个时候会调用Destroy()。这个时候m_pPointer是没有被赋值的。如果你没有正确的初始化的话,delete m_pPointer就会造成问题。
因此,正确的做法应该是这样:
void CMyClass::Init()
{
m_pPointer = NULL;
m_pPointer = new CMyOtherClass;
}
void CMyClass::Destroy()
{
if (m_pPointer) delete m_pPointer;
}
当然,初始化也可以在constructor里面做。
这个故事告诉我们,在desktop里面看起来铁定会被执行的东西,在嵌入世界里可能会跑得没影。你要考虑到一个new,一个alloc,一个调用了这些东西的函数等,都有可能回不来。因此,在你准备随时“就义”之前,请安排好“后事”。
其实每个变量要初始化,这是常识。但是有的时候,看起来没问题的偷懒,就有可能引起严重问题。
2。防止内存孤儿的产生
另外一种情况,不是“秒杀”你的应用,而是会慢慢的让你的应用瘫痪掉,这就是内存泄漏。内存泄漏的一个原因是你忘了在合适的时候去清除你申请的内存了,这怪你自己太粗心。不过有的时候,可能由于你意想不到的原因,会导致“内存孤儿”的产生,也就是没人管、管不到的内存,从而造成内存泄漏。
举一个例子:
CMyClass::CMyClass()
{
m_pPointer = NULL;
m_pPointer = new CMyOtherClass;
}
CMyClass::~CMyClass()
{
if (m_pPointer) delete m_pPointer;
}
请注意我们已经吸取了上面讲的问题的教训,正确的初始化了,也能正确的清除。但是,以下的代码:
CMyClass* p = new CMyClass;
...
如果new CMyClass成功,但是在构建CMyClass的时候,new CMyOtherClass失败,会怎样呢?新创建的CMyClass对象里面的m_pPointer会保持为NULL,这个没问题,但是,CMyClass对象的指针,放到哪里去了呢?
答案是哪里也没有放,因为还没来得及赋值给p,程序执行就走了,离开了,不回来了。你制造了一个孤儿。这片内存(里面放着一个构建失败的CMyClass),永远没有机会被释放了,除非整个内存管理器被清除。
要怎么解决呢?方法就是两步构建。第一步,构建对象本身,第二步,构建对象内容。
改成这个样子:
CMyClass::CMyClass()
{
m_pPointer = NULL;
}
void CMyClass::Init()
{
m_pPointer = new CMyOtherClass;
}
调用的时候:
CMyClass* p = new CMyClass;
p->Init();
...
这样,无论Init()是否成功,p里面都放了CMyClass的指针。
其实我举的例子还是有问题的:如果p->Init()不成功,那么程序走了,临时变量p也会不见掉。正确的做法应该是将指针放到一个外部可以随时存取的context的成员中:
pContext->m_pPointer = new CMyClass;
pContext->m_pPointer->Init();
...
以上讲的两件事情,都不是什么大事,但是细节不注意,会给你们的代码带来大问题。请大家注意养成良好的设计和编码习惯,稍微注意一下,我们一定能渡过内存危机!
附: 上面的这两种思想在iphone sdk中有了很好的体现, 特别是第二件事,
Object *obj = [[Object alloc] init]; //这个其实这个就是一个两步构件的思想。
在iphone开发过程中使用了C++代码的兄弟们就更应该注意这个安全隐患。