ITPub博客

首页 > Linux操作系统 > Linux操作系统 > cleanup stack 和 two phase consturction [2](转)

cleanup stack 和 two phase consturction [2](转)

原创 Linux操作系统 作者:jcszjswkzhou 时间:2019-04-21 16:24:07 0 删除 编辑

二、Cleanup Stack

1、简介
当leave发生时,函数的控制(control)立刻返回给调用时的trap harness中,通常由线程的Active Scheduler缺省的Trap harness处理。也就是说,任何在函数内声明的自动变量都在Trap harness内被销毁。
Active Scheduler 是一个Symbian OS 内核的一个核心对象,它包括当前正被激活的对象列表,并管理这些对象。
但是如果这些自动变量为分配在heap上的指针,就会出现问题。当函数leave发生时,指针被销毁,而指针指向的对象被orphaned(野指针),这是就会出现内存泄漏。Symbian OS使用cleanup stack来防止内存泄漏。

2、内存泄漏问题

当你调用了一个可能leave的函数,那么把pointer首先压入(push)到cleanup stack上。如果函数leave,那么cleanup stack压入的pointer被删除,内存被释放。看看下面的例子:

void SendMessageL()
{
MyObject * object = new (ELeave) MyObject();
SendL(object); // 函数leave时,会产生内存泄漏
}

首先可以看到,MyObject object对象是分配在heap上的,所有对这块内存区域的reference是通过本地的指针变量"object"来实现的。然后,调用了一个可能leave的函数SendL。如果此时这个函数leave,那么一个异常被抛出,那么会遍历call stack,直到一个异常处理函数(Trap或者TrapD)被发现为止。那么对于分配在heap上的object对象会发生什么呢?本地的指针变量"object"已经不存在了,所以这块内存区域不能重新获得使用了。这就是内存泄漏。

3、内存泄漏问题解决之道——cleanup stack

下面是一个使用cleanup stack的例子:

void SendMessageL()
{
MyObject * object = new (ELeave) MyObject();
CleanupStack::PushL(object); // 把object指针压入cleanup stack
SendL(object);
CleanupStack::PopAndDestroy(object); // 如果没有leave发生,则弹出object
}

在这个例子中,在MyObject object创建完成之后,马上把指针压入cleanup stack。现在如果SendL() leave,那么cleanup stack会释放其上的内存空间。如果没有leave发生,那么就要从cleanup stack上弹出对象。
Cleanup stack和通常的stack类似,都是从顶端弹出的。另外需要注意的是:不要把成员变量压入cleanup stack。这些对象的销毁通常由父类对象的destructor负责。

三、两阶段构造函数(two phase construction)

当一个对象的创建过程不会发生leave,那么就适合采用传统的由new操作符自动激活C++构造函数。如果一个对象的构造函数可能leave,那么这个对象就要压入到cleanup stack上,或者这个对象的指针必须保存在一个对象中,使得当leave发生时,这个对象能够被销毁。为实现这些,可能产生leave的构造函数就不能采用传统的C++构造函数,而是应该采用two phase construction。这种two phase construction函数通常用于CBase派生类的构造函数中,或者是那些组合对象(compound objects)的构造函数中。组合对象是指一个类对象的成员也需要分配到heap上。

下面举例说明:

// The MyObject Constructor
MyObject::MyObject()
{
MyMemberData * data = new (ELeave) MyMemberData();
}

上面那个例子中的构造函数在heap上连续分配两个对象,所以这两个对象都可能leave,调用这个函数可能的结果如下所示:
<1> 对于MyObject object对象,成功在heap上分配了内存空间。
<2> 在试图给MyMemberData对象在heap上分配空间时,系统内存不足,这时constructor产生leave。
<3> 当leave发生时,那么call stack会试图寻找合适的trap harness。
<4> 在给MyObject object对象返还指针之前,调用construction的call frame已经从call stack上弹出了。所以我们现在没有办法获得MyObject object对象的指针,这时,内存泄漏发生了。

Symbian OS采用two phase construction的方式来处理这类问题。通常two phase construction的步骤如下:
1.使用 new 方法为对象分配内存,如果内存溢出那么 leave 。
2.随意编写一个C++ 构造函数,但是要求这个构造函数不会发生 leave 。
3.把指针推入一个对象中,或者把指针存入到一个支持清理栈的类中。
4.使用第二阶段的构造函数,在这个函数中允许 leave 。

注意:
1.整个执行顺序通常被封装到一个静态成员函数 NewL()和NewLC()。
2.抽象类是不可以被实例化的,所以它就没有 NewL()和NewLC()函数。
3.上面的步骤2是可选的,因为这样的函数完全可以由第二阶段的构造函数替代。这种构造函数只有在不需要使用父类构造函数时定义。
4.按照约定,第二阶段的构造函数通常明明为 ConstructL() 。

这里有两个Two phase construction的例子:

// Phase #1
MyObject::MyObject()
{}
// Phase #2
void MyObject::ConstructL()
{
MyMemberData * data = new (ELeave) MyMemberData();
}

// Put both phases together in one function...
MyObject * MyObject::NewL()
{
MyObject * self = new (ELeave) MyObject();
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop(self);
return self;
}

从上例中可以看出,第一阶段首先调用MyObject::MyObject()给MyObject分配内存空间。如成功,则接下来调用consturctL()来给成员变量分配空间。
NewL() 函数把two phase construction组合为一个新的函数。当MyObject安全创建后,这个对象的指针被压入堆栈。然后调用第二步构造函数consturctL() 用于给成员变量在和heap上分配空间。如果leave,那么保存在cleanup stack上的MyObject会被成功释放。如果没有发生leave,那么会从cleanup stack上弹出MNewLyObject,控制权交给NewL函数。


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10294527/viewspace-126409/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2007-08-29

  • 博文量
    2222
  • 访问量
    1606606