Symbian程序内存管理

在Symbian OS中,有3中方法来处理内存泄露:捕获装置TRAP和TRAPD;清理栈(CleanupStack)和二阶构造。
一.TRAP和TRAPD  

  TRAP和TRAPD是两个预定义的宏,作用是捕获函数所产生的异常。

程序:

 

1 void CreateObjectL()
2 {
3 CObject* obj = new (Eleave)CObject;
4 }
5 Tint err;
6 TRAP(err, CreateObjectL());

 

  在Symbian OS中,如果一个函数产生异常,系统会立即返回到它的上一级调用函数中。因此,如果函数CreateObjectL()函数中产生异常,这一异常将会被TRAP语句中所捕获。

  TRAP需要两个参数,第一个TInt类型的参数,用于保存异常代码,如果没有出现异常,则err值为0或者KerrNone;如果有异常,则err将为一个代表一类异常的负数。第二个参数为调用函数,一旦调用函数出现异常,它将立刻返回到TRAD中,出现异常语句后的所有代码将不再执行。

 

  TRAPD和TRAP两者仅仅有一点区别,在调用TRAPD前不需要定义err变量。

  TRAP和TRAPD在捕获到异常之后,程序员需要对这一异常进行处理,例如使用__ASSERT_ALWAYS(!error, User::Panic(KTxtEPOC32EX,error).

 

二.清理栈CleanupStack
  
  清理栈用于防止函数产生异常时的内存泄露
  
  下面通过一段代码来说明为什么需要使用清理栈

 

void doExampleL()
{
CExample
* myExample = new (ELeave)CExample;
myExample
->iInt = 5;
myExample
->DoSomethingL();
delete myExample;
}

 

  这段代码中,首先在堆中new一个CExample*对象myExample,然后对myExample成员变量进行赋值,并且调用成员函数DoSomethingsL(),调用完成后,删除对象所指的堆空间。看起来似乎完美,但是仔细分析就会发现,DoSomethingsL()函数调用会发生异常,如果函数调用时,发生异常,函数会立即异常退出。在函数退出之前,它会释放函数中所有的参数和局部变量,也就是说对象myExample被释放掉,但是它所指向的空间却来不及释放,这样就产生了内存泄露。也许有人会问,不是有delete的么?但是函数在调用DoSomethingsL()处异常退出,后面的Delete语句没有机会执行,也就没有办法释放相应的堆空间,从而造成上面所描述的内存泄露。
  
  在Symbian OS中,通过CleanupStack来处理这类内存泄露
  修改后的程序如下

 

 

1 void doExampleL()
2 {
3 __UHEAP_SETFAIL(RHeap::EDeterministic, allocFailNumber);
4 CExample* myExample = new (ELeave)CExample;
5 myExample->iInt = 5;
6
7 CleanupStack::PushL(myExample);
8 myExample->DoSomethingL();
9 CleanupStack::Pop()
10 delete myExample;
11 }

 

这里在调用DoSomethingsL()之前,将对象myExample压入清理栈,从而确保程序在执行期间,出现异常也不会产生内存泄露。
  
  注意:类的成员不需要压入清理栈中,因为释放成员的空间是由类的析构函数来完成,如果将其压入清理栈,出栈时,会造成两次删除,从而出现两次删除异常。
  
三.二阶构造
   二阶构造是为了防止在构造对象是产生内存泄露。
   二阶构造函数的基本思想是将可能产生异常的代码包含在二阶构造函数中,将不会产生异常的代码放在普通构造函数中。
   
   通过下面的代码来说明为什么需要使用二阶构造。

 

 

1 class CFullTime : CBase
2 {
3 public:
4 CFullTime(TInt aHour, TInt aMinute, TInt aSecond);
5 void Print();
6 ~CFullTime();
7
8 private:
9 TInt iHour;
10 TInt iMinute;
11 TInt iSecond;
12 CDate* iDate;
13 }
14
15 CFullTime::CFullTime(TInt aHour, TInt aMinute, TInt aSecond)
16 {
17 iHour = aHour;
18 iMinute = aMinute;
19 iSecond = aSecond;
20 iDate = new (ELeave)CDate();
21 }
22
23 CFullTime::~CFullTime()
24 {
25 delete iDate;
26 }

 

主函数代码如下

 

 

{
CFullTime
* cf = new(ELeave)CFullTime(10,30,34);
CleanupStack::PushL(cf);
CleanupStack::PopAndDestroy();
//把cf弹栈并且销毁
}

 

分析:它将在堆区域上分配一个CFullTime的实例对象,并将地址赋给cf指针,然后调用类的构造函数来初始化这个对象。考虑这样一个问题,如果构造函数出现异常,那么就会导致没有指针指向成功分配给CFullTime类型对象的区域,那么这块内存就成了孤立内存,无法被释放,这将导致内存泄漏。
  
  二阶构造,顾名思义,就是将构造分为二个阶段:
  第一阶段:普通构造,没有包含可能导致异常退出的代码;
  第二阶段:包含可能导致异常退出的代码。
  
  改造后的程序代码如下:

class CFullTime : CBase
{
public:
static CFullTime* NewL(TInt aHour, TInt aMinute, TInt aSecond);
static CFullTime* NewLC(TInt aHour, TInt aMinute, TInt aSecond);
~CFullTime();

private:
CFullTime(TInt aHour, TInt aMinute, TInt aSecond);
void ConstructL(); //第二阶段构造

private:
TInt iHour;
TInt iMinute;
TInt iSecond;
CDate
* iDate;
}

CFullTime::CFullTime(TInt aHour, TInt aMinute, TInt aSecond)
{
iHour
= aHour;
iMinute
= aMinute;
iSecond
= aSecond;
}

CFullTime
* CFullTime::NewL(TInt aHour, TInt aMinute, TInt aSecond)
{
CFullTime
* self = CFullTime::NewLC(aHour, aMinute, aSecond);
CleanupStack::Pop(self);
return self;
}

CFullTime
* CFullTime::NewLC(TInt aHour, TInt aMinute, TInt aSecond)
{
CFullTime
* self = new (ELeave)CFullTime(aHour, aMinute, aSecond);
CleanupStack::PushL(self);
self
->ConstructL(aHour, aMinute, aSecond);
return self;
}

void CFullTime::Construct()
{
iDate
= CDate::NewLC();
CleanupStack::Pop(iDate);
}

CFullTime::
~CFullTime()
{
delete iDate;
}

 

在这里,遇到几点疑问:
 1.在生成这个对象指针的时候,到底是用NewL呢还是用NewLC呢?
 答:如果对象的实例是作为类成员的,那么用NewL,如果是局部变量,那么用NewLC,用完后Popanddestory 
 
 2.NewLC方法PushL了一个对象,却为什么没有Pop出来?
 答:NewLC中用pushL,就是为了调用ConstructL方法。在ConstructL中使用this指针即可调用该对象的其他方法。PushL之后没有Pop配对,是为了让外界(实例化该对象的其他对象)继续调用该对象的其他L方法,外界使用完该对象后Popanddestory之。
 
 3.如果用NewLC的话,在对象销毁的时候是否用该手动Pop一下?
 答:是的。如果你直接调用NewLC函数,最后销毁对象的时候肯定要Pop一下。

posted @ 2010-12-21 12:05  狼哥2  阅读(170)  评论(0编辑  收藏  举报