Symbian C++开发,误用NewLC导致的KERN-EXEC 3异常

最近在一个Symbian S60工程中,程序退出时总是提示KERN-EXEC 3错误,跟踪调试是在对象析构时发生的异常,
但定位不到任何具体的行上面,所以推测是系统自动析构某个成员时发生的异常。

注:KERN-EXEC 3 错误描述
This panic is raised when an unhandled exception occurs. Exceptions have many causes, but the most common are access violations caused, for example, by dereferencing NULL. Among other possible causes are: general protection faults, executing an invalid instruction, alignment checks, etc.


然后在析构函数中逐步的去掉一些对象的销毁操作,最后发现与一个指针对象(iMyObject)的delete有关,去掉这个iMyObject对象
的delete操作后,跟踪发现,iMyObject的析构函数仍然会被调到,奇怪,系统自动进行销毁?恍然大悟,CleanupStack!

马上查看iMyObject的构造:


iMyObject 
=  CMyOjbect::NewLC();

原因找到了。不过还要对NewLC费点口舌,它是这样定义的:

CMyOjbect *  CMyOjbect::NewLC()
{
    CMyOjbect
* self = new ( ELeave ) CMyOjbect();
    CleanupStack::PushL( self );
    self
->ConstructL();
    
return self;
}


注:‘LC’后缀表示 may Leave and Cleanup.

这是典型的Symbian两段构造,NewLC在调用new构造对象后,再调用对象的ConstructL,为了保证在ConstructL可能发生的Leave情况下
安全析构self对象,调用前,将seft指针Push到CleanupStatk中。

我在先前的工程代码中,虽然调用了NewLC构造iMyObject对象,但是始终没有调用过CleanupStack::Pop()将对象清出栈,以致于工程
退出时,CleanupStack自动清除其内对象,导致重复销毁对象,产生了KERN-EXEC 3错误。

解决办法就是改用NewL来构造对象:

CMyOjbect *  CMyOjbect::NewL()
{
    CMyOjbect
*  self  =  CMyOjbect::NewLC();
    CleanupStack::Pop(); 
//  self;
     return  self;
}


NewL首先调用NewLC构造,然后调用CleanupStack::Pop()将刚刚进栈的iMyObject对象出栈,这样程序退出时,我们自己delete一次iMyObject
就不会有问题了。

关于使用CleanupStack清除对象,以及选择什么实际进行Pop,在《Symbian OS Explained Effective C++ Programming for Smartphones》一书
中也有描述,摘录如下:

At times, it’s quite difficult to keep track of what’s on the cleanup stack.
Imbalance bugs can be difficult to find because they cause unexpected
panics in code quite unrelated to where the error has occurred. To avoid
them, it’s best to name what you Pop() off the cleanup stack explicitly,
if only in the early stages of coding until you’re confident that the code
is behaving as you expect. The checking is only performed in debug
builds, so it has no impact on the speed of released code (although, if
you do want to perform checking in a release build, you can use the
CleanupStack::Check() function). If nothing else, it may help you
track down bugs such as the one below where, under some conditions, an
object is accidentally left on the cleanup stack when the function returns:

     void  AnotherContrivedExampleL( const  TDesC &  aString)
    {
        CClanger
*  clanger  =  AllocateClangerL(aString);
        CleanupStack::PushL(clanger);
        TBool result 
=  NonLeavingFunction(clanger);
        
if  ( ! result)
        
return //  Whoops, clanger is still on the cleanup stack!
        DoSomethingThatLeavesL();
        CleanupStack::PopAndDestroy(clanger);
    }


But when should you Pop() an object from the cleanup stack? Well, it
should never be possible for an object to be cleaned up more than once.
If a pointer to an object on the cleanup stack is later stored elsewhere,
say as a member variable of another object which is accessible after a
leave, it should then be popped from the cleanup stack. If the pointer
were retained on the cleanup stack, it would be destroyed by it, but the
object storing the pointer to it would also attempt to destroy it, usually in
its own destructor. Objects should be referred to either by another object
or by the cleanup stack, but not by both.

     void  TransferOwnershipExampleL
    {
        CClanger
*  clanger  =   new  (ELeave) CClanger();
        CleanupStack::PushL(clanger); 
//  Pushed - the next function may leave
        iMemberObject -> TakeOwnershipL(clanger); //  iMemberObject owns it so
        CleanupStack::Pop(clanger);  //  remove from cleanup stack
    }

  
Likewise, you should never push class member variables (objects
prefixed by ”i”, if the Symbian OS naming convention is followed) onto
the cleanup stack. The object may be accessed through the owning object
which destroys it when appropriate, typically in its destructor. Of course,
this requires the owning object to be leave-safe.
Incidentally, no panic occurs if you push or pop objects onto the
cleanup stack more than once. The problem occurs if the cleanup stack
tries to destroy the same object more than once, either through multiple
calls to PopAndDestroy() or in the event of a leave. Multiple deletion
of the same C class object will attempt to free memory which has already
been released back to the heap, which causes a panic.


总结:Symbian C++是为内存受限系统设计的开发环境,所以很多地方的处理需要格外注意。特别是
从Win32或者Linux转过来的开发人员,开始会很不适应这种处理方式,但是久而久之,你就会发现,
这种设计是有他的道理和益处的。从代码形式上,Symbian C++,强迫你提高对内存和异常处理的警惕
和重视程度。
 

你可能感兴趣的:(Symbian C++开发,误用NewLC导致的KERN-EXEC 3异常)