Symbian中内存管理

来源:互联网 发布:手机预约挂号软件 编辑:程序博客网 时间:2024/05/01 14:38

Symbian中内存管理

内存管理(以下内容来自symbian网站的技术文档)
对内存检测来说,一个很重要的错误检查工具就是堆平衡检查方式,代码如下:
__UHEAP_MARK;
SomeFunction();
__UHEAP_MARKEND;
它的主要工作就是检查堆的状态在函数(SomeFunction())调用前与函数调用后是否是一样的,如果不一样,那EPOC将会向运行该代码的线程发出系统错误——可能会突然中断它。不过这是在调式模式下才能生效的。

在EPOC c++中检测可能引起异常的函数一般用如下代码:
TInt err;
TRAP(err,FooL());
if (err)
{
Bar();
}
注意,这里Bar()不能引起异常。

在EPOC C++中,对new的操作会使用ELeave参数,这是其所特有的,如
x = new(ELeave) CWidget(y,z);
它的含义是在new操作时拥有异常检查,以便及时阻止错误的发生,其具体的重载代码可能看起来如下:
x = STATIC_CAST(CWidget*, User::Alloc(sizeof(CWidget)));
if (!x)
User::Leave(KErrNoMemory);//Leaves the currently executing function, unwinds the call stack, and returns from the most recently entered trap harness.
Mem::FillZ(x, sizeof(CWidget));
x->CWidget::CWidget(y,z);

因此如果所需内存不能被分配了,那就会调用User::Leave(KErrNoMemory)。
一个L后缀的函数不是调用另一个L函数就是包含new(ELeave)操作,或者就是直接含有User::Leave()。

标准C++的异常处理包括自动的释放,比如,在堆栈上操作的函数如果抛出一个异常,那就会释放这次异常操作的部分,比如
widget x;
x.foo();
也就是,如果foo()中发生了错误,那就会释放x,但在EPOC中,我们是没有这种自动的机制的,所以我们采用清除栈:
CWidget* x = new(ELeave) CWidget;
CleanupStack::PushL(x);
x->FooL();
CleanupStack::PopAndDestroy() // x
它是怎么样工作的那?
1、如果new(ELeave)调用失败,则不会分配任何内存,也没有任何东西需要被清除。
2、一个指向CWidget对象的指针被推入到清除栈中。
3、如果FooL()发生了异常,则CWidget对象是在清除栈上的,因此它被释放——调用它的析构函数,然后释放它的内存。要完成这些工作,该对必须从CBase派生,它包括虚析构函数。CleanupStack::PushL()实际要?木褪荂Base*参数。
4、当完成了x的相关操作后,我们就要pop它,并且要删除它,我们可以在一个函数中做到,就是CleanupStack::PopAndDestroy()

由 于清除栈自己有内存操作,因此PushL()可能会调用失败。PushL() ensures that the object is placed on the stack before trying to create a spare slot for the next PushL() - so that, if there is insufficient memory to create the new slot, the CWidget is cleaned up properly.

从CBase派生的对象应该分配在堆上,如果将他们分配在堆栈上则是非法的,从EPOC对new的重载我们就可以看出端倪来。
一般认为栈是比堆更有效率的,但在EPOC中堆要比大多数系统中的堆更有效率,因为在EPOC中,堆不在进程的线程中共享,是为每个线程所单独拥有的。那就意味着new和delete操作更加灵便,因为他们不需要serialize heap allocation.
在EPOC中的多任务是用活动对象来代替多线程的。

如果内存发生异常,则double delete有可能发生。你绝对不能把一个成员变量放入到清除栈中(这个我们之前已经再三说过,参见前面的日记)那将导致double delete,因此这个变量将被包含它的对象的析构函数和清除栈本身都删除一次!

总之你绝对不能把i前缀(约定束成是成员变量)的变量push到清除栈中。

这 里提醒一下,在标准c++中同样要避免这样的问题。because an object referred to by both an automatic, and a member of a class, may be deleted twice by C++’s exception processing. 标准c++的auto_ptr模板类提供了一定的控制,可以使用它编写安全的代码。

有些对象不拥有外部资源,因此并不需要析构函数。
典型的例子如内建的类型,int, double等,还有简单的组合类型如point,size(都是两个int)或rectangle(四个int)等。

EPOC 使用T前缀来表明没有析构函数的类型,内建对象也按照这个原则重新包装了一下:TInt(for int)和TReal(for double),Class types包括TPoint, TSize和TRect以及基本的字符串,从TDesC派生的支系,TDesC表示“常量描述符”。
T object是不需要清除的。它也没有一个通用的基类(如CBase)。T object经常驻于堆栈上,或是成为其他类的成员变量。
当然你也可以将其分配在堆上,你必须使用另一种版本的Cleanup::PushL(),使用TAny*(ie, a void*)参数版本,如
TInt* pn=new(ELeave) TInt;
CleanupStack::PushL(pn); // pushes a TAny*
CWidget* x = new(ELeave) CWidget;
CleanupStack::PushL(x); // pushes a CBase*
x->FooL();
CleanupStack::PopAndDestroy(2); // delete x, User::Free(pn)

清除一个T对象时是不会调用其destructor的(它也没有),只是释放分配给该对象的内存而已。

现在再来看看EPOC的命名约定:
T
prefix for classes or types which don’t need a destructor

C
prefix for classes derived from CBase, with a virtual destructor, always allocated on the heap

i
prefix for member variables. Don’t ever push these to the cleanup stack.

L
suffix for functions which might leave. Make sure any code that calls these takes appropriate cleanup precautions.

在 EPOC中,C类是通过referencr传递的,而T类可以通过refrence也可以通过值传递,如果他们是通过值传递的,那他们几乎总是按位拷贝 的,作为结果,没有必要去定义赋值或拷贝操作符。因此,在EPOC C++中不需要赋值运算符(重载)和拷贝构造函数,这是和标准C++最主要的不同之处(让人惊异之处),特别是在他们被用于隐藏底层的拷贝时,此时,这种 拷贝可能导致内存越界并产生一个异常在EPOC中,你可以认为a = b; 不会发生异常。

做为个准则,在EPOC中,C++构造函数是不能含有可能引起异常的函数的,如果有的话,那必须采用双重构造来解决这个问题。我们一般使用ConstructL()来完成这个任务。

还记得析构函数吗?
CWidget::~CWidget()
{
delete iDoodad;
delete iWotsit;
}

这里如果没有分配iDoodad或iWotsit(构造时发生了异常),那就将如何析构那?答案是delete 0,C++语法中delete 0不做任何动作。
为 什么是0那,你可以认为ConstructL()或是CWidget的构造函数已经明确的进行了初始化0的操作(在任何可能引起fail的操作前)。事实 上,CBase::operator new()(以及CBase::operator new(TLeave))初始化了所有分配的内存为0,这就是部分分配的对象能被正确清除的原因。

许多的类都定义一个可反复重用的厂函数,由它来生成对象,封装C++和双重构造。一般定义为NewLC()和NewL()
CWidget* x=CWidget::NewLC();
x->FooL();
CleanupStack::PopAndDestroy(); // x

or

iWidget=CWidget::NewL();
iWidget->FooL();

一般LC版本用于自定义的指针,而L版本则用于成员变量中。道理上面已经说过了,为了防止双重deleted.


在GUI framework中
active scheduler调用RunL()函数来运行活动对象以处理事件,这事件也是由它分派到程序中的。如果RunL()发生异常,那Error()函数就会被调用,它将弹出一个对话框给用户,显示引发这个环境错误的信息。

R类如RTimer,RFile和RWindow,他们可以使用值传递的方式,也可以驻于栈中,或做为成员数据,一般情况下,他们不会直接驻于堆上。R对象没有析构函数,但取而代之的是Close()函数,它用来关闭服务端的资源以及与之联系的客户端session。

原创粉丝点击