摘要:错误处理和清理工作对于Symbian OS环境下的开发显得尤为重要。错误处理(error handle)实际上是为了编写出更可靠的程序,而清理(cleanup)框架则是Symbian OS的编程基础,正因为如此,必须保证错误处理和清理非常有效且易于实现。
OOM(out-of-memory)错误在Symbian OS环境下则不得不谈。目前的台式机内存的容量越来越大,加上更大硬盘上创建的虚拟内存,而且使用者可能进行经常性的重启。在这样的情况下,内存耗尽很少发生。而在Symbian OS的环境下,RAM少到只有4MB,一般也不会超过16MB,使用时经常要求不必重启。所以我们在Symbian OS环境下开发要注意以下问题:
必须高效编程,使程序不会浪费RAM。
必须尽快释放资源,因为不可能不释放资源,让运行程序消耗越来越多的RAM。
必须处理内存不足错误。在Symbian OS中这类错误可以随机发生。
若出现内存不足错误,导致某些操作停止,一定不能丢失用户数据。
若某个要分配若干个资源的操作中途发生了内存不足,必须清理所有这些资源。
实际上Symbian OS的错误处理和清理框架并不只对OOM错误有效,很多其他操作因为其环境条件可能失败,比如读写文件、打开文件,在通信会话中发送和接受数据。也可以使用错误处理和清理框架来处理。
下面列举一些用于处理内存不足错误和测试OOM的工具:
最普通的调试键
常用的Ctrl+Alt+Shift+(A使用堆单元、B文件服务器资源、C窗口服务器资源)
堆检查工具,用来检查函数分配的资源是否释放
C++的析构函数来销毁对象
堆失败工具,故意产生错误
异常退出机制,用来指示错误。其中一个基本函数User::Leave()
清理栈
异常捕获装置:用陷阱捕获异常退出的过程,类似与C++中的try-catch机制
CBase类,所有C类的基类,由清理栈来识别,包含一个C++析构函数
双阶段构造函数
命名约定
以上所述的工具接下来部分会做一介绍:
堆检查:
主要是使用宏_UHEAP_MARK和_UHEAP_MARKEND。
宏_UHEAP_MARKEND主要在析构函数中调用,此时在程序中任何位置如果包含了宏_UHEAP_MARK,则可以检查中间的堆操作是否平衡,就是说如果堆单元的数目和调用_UHEAP_MARK时数量不同,程序就会出错。
析构函数
想大家都不陌生,只是简单提一下
程序中使用过的对象,并不是所有的都要在析构函数中去释放,所销毁的只是自己所拥有的对象,也就不包括临时生成的局部变量。
堆失败工具
这个工具主要用来处理内存不足的错误,有个名为memorymagic的应用程序就是这个作用的,但不是所有的symbian系统都有相应的这个应用
具体使用方法可以到symbian的官方网站上下载这个东东,里边有相应的使用说明。
异常退出机制
当然就是使用User::Leave()来根据不同的错误码(请参照本blog中的Symbian错误码)异常退出,User::Leave()函数可以让活动函数的运行终止,并且接着终止所有调用函数。
清理栈
清理栈解决如下问题:清理栈在堆上分配的,但是指向它的唯一指针却是自动变量的对象。如果分配对象的函数异常退出,则需要清理对象。例程
case EMagicCmd:
{
CX* x = new (ELeave) CX;
CleanupStack::PushL(x);
x->UseL();
CleanupStack::PopAndDestroy(x);
}
上述例程无论UseL()运行是否异常,程序都可以正常退出,因为作为异常处理的一部分,弹出并销毁清理栈上的所有对象是必须完成的。当然你也可以这么做
case EMagicCmd:
{
CX* x = new (ELeave) CX;
TRAPD(error,x->UseL());
if(error)
{delete x;
User::Leave(error);
}
delete x;
}
注意:没有必要不要使用清理栈
我们只需要使用清理栈来阻止越过对象的析构函数,如果该对象的析构函数一定会被调用,那么就一定不要使用清理栈。
一般来说类的成员变量往往都可以被类自身的析构函数销毁,所以绝对不应该把成员变量推入清理栈,避免二次删除!
两阶段构造
清理栈用于保存指向基于堆的对象的指针,以便发生异常退出时进行清理。这意味着,必须有机会来把对象推入清理栈。这个问题就可以利用两阶段构造来解决。
首先请记住C++的构造函数不应该包含任何可能异常退出的函数。例程:
class CY : public CBase
{
public:
CY();
~CY();
public:
CX* ix;
CY:CY()
{
ix = new(ELeave) CX;
}
CY:~CY()
{
delete ix;
}
}
调用
CY* y = new(ELeave) CY;
CleanupStack::PushL(y);
...
CleanupStack::PopAndDestroy(y);
这个时候在分配CY的过程中,同时要分配一个CX,由于构造函数CY()没有异常处理机制,因此一旦分配CX失败,则得到内存泄漏错误,因此需要利用一个完全独立的函数来实现这个。于是就定义了ConstructL()
class CY : public CBase
{
public:
~CY();
static CY* NewL();
static CY* NewLC();
void ConstructL();
public:
CX* ix;
CY:~CY()
{
delete ix;
}
}
void CY:ConstructL()
{
ix = new(ELeave)CX;
}
调用改为:
CY* y = new(ELeave) CY;
CleanupStack::PushL(y);
y->ConstructL();
...
CleanupStack::PopAndDestroy(y);
很显然这时ConstructL()能够安全清理。
一些经验小节:
对象不可分配两次, 不可删除两次。
delete ib; //加上
ib = NULL; //加上
ib = new CB;
不要删除非拥有对象
决不要从C++构造函数中异常退出。
使用好命名规范,帮助自己理解程序。
用好清理栈。
用好宏TRAPD()同User:Leave()。
-----------------------------------------------------------
总之,Symbian OS下的错误处理和清理是异常复杂和难以琢磨的,只有用我们细心加耐心去慢慢克服。
--风小云原创