symbian中memory management,error handling,构造及析构

 

 至于为什么需要内存管理,不在此文描述中,可以另外查找资料了解,此文介绍的是方法,即如何管理。

一般而言,和内存有关的new应该都是了解的了,也知道new以后,有可能会存在内存分配不够的问题,那么分配不够的话,new就会返回一个NULL指针,也是众所周知的。这个NULL判断就是C/C++中的内存泄漏的管理方案。那么symbian怎么做呢?

symbian采用的是leave的方式,什么是leave?也就是一种轻量级的抛异常处理。由于symbian内存较小,异常处理需要较大的代码量,在早期的symbian版本中也不支持异常。所以symbian使用了一种leave和trap的方式来代替异常处理。symbian OS 9.X 版本支持异常处理,但是仅推荐使用与应用程序和其他系统导出库。

leave就类似于抛异常,而trap则可捕获异常并进行处理,类似try-catch形式。一个leave常被链接到一个错误代码上,比如User::LeaveNoMemory();User::LeaveIfNull(myObject);User::LeaveIfError(error);等等。

如果一个函数leave了,那么这个函数已经终止。

leave如果未被处理,则会被向外传递,直到处理为止。实际上,通常异常被直接传递给下一个trap。

所以如果一个函数包含了另一个leave函数,则也可能会leave。

有时调用某些系统静态函数也会导致leave。

new已经被重载了一个leave的版本。则我们可以使用

CObject *myObject = new ( ELeave ) CObject();来定义之前的变量。使用这个语句则有可能会导致leave。
如果我们希望直接抛出一个指定的异常可以使用比如User::Leave( KErrNotFound );
常用的一些常量:
 
KErrNone             0  no error
KErrNotFound        -1  some item is not found
KErrGeneral         -2  error with no categorisation
KErrCancel          -3  an operation has been cancelled
KErrNoMemory        -4  memory allocation failed
KErrNotSupported    -5  functionality not supported in context
KErrAlreadyExists   -11 object with same name or type exists
KErrDied            -13 using a handle to thread that has died
KErrAccessDenied    -21 no required permissions to resource
KErrLocked          -22 file to read or write is locked
KErrDiskFull        -26 cannot write because disk is full
KErrBadName         -28 object name does not conform to syntax
KErrTimedOut        -33 an operation has timed out
KErrCouldNotConnect -34 could not connect a session
KErrDisconnected    -36 required session was disconnected
KErrAbort           -39 an operation has been aborted
 
 在可能导致leave的函数且未使用trap进行关闭之前其函数名后加一个"L"以做提示作用。
 
trap又是怎么一回事呢?
在leave后需要处理该leave异常。就定义了一个宏来进行处理。可以使用两个宏定义作为trap:
TRAP()和TRAPD,两者的区别在于前者需要另外定义一个变量接收抛出的error代码,后者则直接使用参数中的名字。
一个例子:
TRAP: 
TInt error ;
 
TRAP( error, DoExampleL() ) ; // var error must exist
if ( error != KErrNone )
  { 。。。
  }
TRAPD:
TRAPD( error , DoExampleL() ) ;
一句话就代替了TRAP上面的代码。
 
上面既然说到未使用trap则会一直传递,那么肯定会问,如果传递到最上层怎么办呢?
别急,系统定义了一个默认的trap机构就是专门处理这些被人忽视的leave的。
如果leave是在应用程序启动时抛出的leave,那么应用程序会被直接关闭,如果应用程序已经启动完毕,那么只会终止当前操作,并返回事件控制循环。
这下清楚了,可以自定义trap处理或是自定义何时抛异常,同VC差不多,但是因为自定义异常会增加代码量,所以如果不是必须就尽量使用系统默认的处理好了。
leave讲的差不多了,大家都知道在C++如果需要new一个对象,就会调用构造函数,也就有了一个问题,如果在构造的时候new出问题怎么办?
symbian想了一个办法,就是把new分成两步走,把会造成leave的全部分到一起,作为另外一个函数,名字规定为ConstructL。ConstructL必须简短,构造函数就只管初始化好了。
这个时候要想new一个对象,都必须记得调用ConstructL,造成了一定的麻烦,那么干脆把它们整合成一个NewL好了,再把构造函数和ConstructL都私有化,这样就可以防止人们胡乱调用构造函数了。如果要代替new,那么NewL必须是一个静态函数,否则对象都没构造出来怎么调用呢。
好了,办法都想好了,那么开始使用NewL好了。  
CObject* CObject::NewL( TInt aSize )
  {
  CObject* self = new ( ELeave ) CObject;
  self->ConstructL( aSize );
  return self;
  }
不错吧,不过还是有问题,如果在ConstructL中leave了,那么NewL也就leave了,那我们new的那个self怎么办?为了解决这个问题,在symbian中有了一个解决办法,利用 CleanupStack  
CObject* CObject::NewL( TInt aSize )
  {
  CObject* self = new ( ELeave ) CObject;
  CleanupStack::PushL ( self );
  // if ConstructL leaves, self is deleted automatically
  self->ConstructL( aSize );
  CleanupStack::Pop ( self ); // pop when cannot leave anymore
  return self;
  }
在异常退出的时候,自动清除在栈内的指针。
CleanupStack::Pop(clanger);
delete clanger;
等价于:CleanupStack::PopAndDestroy(clanger);
每个对象只能被清除一次,否则会产生错误。所以绝对不能push类成员变量。
对象必须以严格的顺序压入和弹出清除栈,一组Pop()操作必须与PushL()调用的顺序相反。可能会发生调用Pop()或PopAndDestroy()弹出对象而没有命名这些对象的情况,但最好还是要对弹出对象进行命名。可以使用CleanupStack::PopAndDestroy(4);或CleanupStack::PopAndDestroy(4,sealPoint);前者是指弹出最后入栈的4个指针并销毁;而后者是指明最后弹出的指针。
如果一个函数退出时,仍然有数据存在于CleanupStack栈上,该函数名末尾应以"C"结尾,以做提示。
这就要说到,NewLC这个函数了。
 
CObject* CObject::NewL C ( TInt aSize )
  {
  CObject* self = new ( ELeave ) CObject;
  CleanupStack::PushL( self )
  self->ConstructL( aSize );
  return self; // note that the object is not popped
  }
CObject* CObject::NewL( TInt aSize )
  {
  CObject* self = CObject::NewLC( aSize );
  CleanupStack::Pop( self );
  return self; // note how NewLC is utilized to re-use code
  }
一看就明白了,NewLC没有在函数里将self弹出。不过具体应该将NewLC用于何处,我还不是很清楚,如果有这方面的高手可以稍作提示,大家分享一下。
 
在继承一个类的时候,不要忘记在 ConstructL中调用父类的ConstructL,当然也有定义一个专门的用于继承的函数 BaseConstructL。
void CDerivedObject::ConstructL( TInt aSize )
  {
  CObject::ConstructL( aSize );
  ...
  }
现在谈谈删除对象的问题。为安全考虑,不是自己的不要胡乱删除。delete后记得把指针赋值为NULL,原因大家都明白,防止使用野指针。在更新成员时,也记得先删除指针,再重新new,也是防止内存泄漏的。 
void CObject::UpdateMemberL()
  {
  delete iMemberObject;
  iMemberObject = NULL;
  iMemberObject = CMemberObject::NewL();
  }
总结一下在symbian中的异常处理,三种常用方法:
asserts;返回错误代码;leave和trap。
Panic和Assert:
assert有两种断言__ASSERT_ALWAYS 和__ASSERT_DEBUG。顾名思义后者只在debug时使用,前者则是所有时间段都可以使用。Panic是在有重大错误时,终止线程,若Panic发生了,除非在指定的位置有一个名叫“ErrRd”的文件,否则模拟器不显示Panic的细节。在SDK 3rd版以前,ErrRd文件必须手工创建,但从3rd版以后,这个文件可以默认在目录“C:/Symbian/9.2/S60_3rd_FP1/Epoc32/winscw/c/resource”下找到。如果即使使用3rd版的SDK,ErrRd文件也找不到,那就启动模拟器,选择 Tools > references,然后勾上Extended panic code file。
断言的使用方法如下:
void TestValue(TInt aValue)

 _LIT(KPanicCategory, "TestValue");   

  __ASSERT_DEBUG((aValue >= 0), User::Panic(KPanicCategory, 99));   

  // Do something with aValue   

  // ...   

}

 

可以看有ErrRd文件的样本。

有一个宏可以代替上面的那个宏: 

  #define ASSERT(x) __ASSERT_DEBUG(x, User::Invariant()) 
一个如何使用ASSERT宏的例子:
void TestValue(TInt aValue)
{
ASSERT(aValue >= 0);
...
}
有一些工具可以模仿内存分配失败:  
__UHEAP_SETFAIL (aType, aValue)
其中aType有ERandom,ETrueRandom,EDeterministic,EFailNext。
Deterministic 当第N次请求时失败
ERandom 随机失败种子相同
ETrueRandom 随机失败,种子从系统时间获取
__UHEAP_SETFAIL ( RHeap::EDeterministic, 3 )指每3个分配出错  
__UHEAP_SETFAIL ( RHeap::EFailNext, 1 )下一个分配出错
 
__UHEAP_RESET 必须在最后被调用于重启这些工具。
还有一些工具找到内存泄漏:  
__UHEAP_MARK //标记堆记录起始点
 。。。
__UHEAP_MARKEND //标记结束点
   
__UHEAP_MARKENDC( aCount ) 期待检查结束时堆空间仍保留aCount个新分配对象
__UHEAP_CHECK(aCount) 在当前嵌套层中检查是否新分配了aCount个对象
__UHEAP_CHECKALL(aCount) 检查当前线程中所有新分配对象是否为aCount个
User::Heap.Check() 可能是和上面呢个check是一样的用法吧。
 
ok,总算暂时解决了内存管理方面的问题。如果有高手觉得有哪里不对或是不够好的,请尽管提出,以便更正。
 

你可能感兴趣的:(symbian中memory management,error handling,构造及析构)