Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

3

The Cleanup Stack

Life is pleasant. Death is peaceful. It’s the transition that’s troublesome

Jimi Hendrix

This chapter discusses a fundamental part of Symbian OS, the cleanup stack. Symbian OS is designed to perform well with limited memory, and part of that design must inevitably consider memory management when errors occur. The cleanup stack manages memory which would otherwise be ”orphaned” (leaked) in the event of a leave.

But what, exactly, is ”orphaning”? In the previous chapter, I described why Symbian OS doesn’t use standard C++ exceptions, but instead handles exceptional conditions using ”leaves”. Code that can leave is, at some level, surrounded by a TRAP, or TRAPD, harness. If a leave occurs, control is transferred directly to the statement following the harness. In effect, the TRAP is equivalent to a setjmp and the leave to a longjmp. The stack memory is freed as the stack unwinds, but otherwise the stack frame is abandoned and no object destructors are called (which is unlike a standard C++ exception). This means that the destructors of any local variables or arguments passed by value, or objects created as member variables of either of these, will not be called. Some objects are ”leave-safe” – they do not need destructors and contain only data which can be destroyed as stack memory is freed by the leave. This data may consist of the basic, built-in types or other objects which contain such types. In Symbian OS these are called T classes (T for ”type”, described in detail in Chapter 1), the characteristic of which is that they may safely be created on the stack in functions where code may leave.

If a local variable is a pointer to an object on the heap, when a leave occurs the pointer is destroyed without freeing that heap memory, which becomes unrecoverable, causing a memory leak. The memory is said to be orphaned. This means that C class objects, which are always created on the heap, as described in Chapter 1, are not leave-safe. Unless they are otherwise accessible for safe destruction (for example, as member variables of an object which is destroyed regardless of the leave), the

30

THE CLEANUP STACK

memory they occupy on the heap and any resources they own are orphaned by a leave. R class objects are generally not leave-safe either, since the resources they own must be freed in the event of a leave (through a call to the appropriate Close() or Release() type function). If this call cannot made by an object accessible after the leave, the resource is orphaned.

Consider the following code, which creates an object of a C class (CClanger) on the heap, referenced only by an automatic variable, clanger. After creating the object, a function which may potentially leave, InitializeL(), is called. The heap-based object is not leavesafe and neither the heap memory it occupies, nor any objects it owns, are destroyed if InitializeL() leaves.1

void UnsafeFunctionL()

{

CClanger* clanger = new (ELeave) CClanger();

clanger->InitializeL(); // Potential leaving function orphans clanger

... // Omitted for clarity delete clanger;

}

You could remove the potential memory leak by placing a TRAP, or TRAPD, macro around the call to InitializeL() to catch any leaves. However, as Chapter 2 explained, the use of TRAPs should be limited, where possible, to optimize the size and run-time speed of the compiled binary. I have only shown one call which could cause the object to be leaked; in a typical function there may be other operations that may leave. It would be inefficient to surround each with a TRAP. From an error-handling perspective, too, it’s often preferable to leave rather than return an error, as I discussed in Chapter 2.

When you combine both arguments, and consider the following irredeemably inefficient code, it’s clear that there should be an alternative to the TRAP idiom to protect objects which are not inherently leave-safe:

void SafeButInefficientL()

{

CClanger* clanger = new (ELeave) CClanger();

TRAPD(r,clanger->InitializeL()); // leave-safe, at a cost...

if (KErrNone!=r)

1 Before going any further, I should point out that the example is atypical of how a CBase- derived class should be instantiated. The Symbian OS coding standard recommends that all C classes wrap their construction and initialization code in a single static function, generally called NewL() or NewLC(), as illustrated later. To prevent a client from instantiating and initializing the object separately, as I’ve shown above, the standard advises that the constructors and initialization functions should be specified as protected in the class. This is known as two-phase construction, and is described further in Chapter 4.

USING THE CLEANUP STACK

31

{

delete clanger; // delete clanger to prevent a leak User::Leave(r); // leave with the same error... horrible!

}

// Now do something else that may leave TRAP(r, clanger->DoSomethingElseL()); if (KErrNone!=r)

{

delete clanger; // Again, delete clanger to prevent a leak User::Leave(r);

}

... // Omitted for clarity delete clanger;

}

When a leave occurs, local variables are destroyed without first freeing any resources they own. The resource becomes unrecoverable, causing a memory leak, and is said to be ”orphaned”.

3.1 Using the Cleanup Stack

This is the cue for the cleanup stack, which is accessed through the static member functions of class CleanupStack, defined in e32base.h:

class CleanupStack

{

public:

IMPORT_C static void PushL(TAny* aPtr);

IMPORT_C static void PushL(CBase* aPtr);

IMPORT_C static void PushL(TCleanupItem anItem);

IMPORT_C static void Pop();

IMPORT_C static void Pop(TInt aCount);

IMPORT_C static void PopAndDestroy();

IMPORT_C static void PopAndDestroy(TInt aCount); IMPORT_C static void Check(TAny* aExpectedItem); inline static void Pop(TAny* aExpectedItem);

inline static void Pop(TInt aCount, TAny* aLastExpectedItem); inline static void PopAndDestroy(TAny* aExpectedItem);

inline static void PopAndDestroy(TInt aCount, TAny* aLastExpectedItem);

};

Objects that are not otherwise leave-safe should be placed on the cleanup stack before calling code that may leave. This ensures they are destroyed correctly if a leave occurs; in the event of a leave, the cleanup

32

THE CLEANUP STACK

stack manages the deallocation of all objects which have been placed upon it.

The following code illustrates a leave-safe but simpler version than the

SafeButInefficientL() example:

void SafeFunctionL()

{

CClanger* clanger = new (ELeave) CClanger;

//Push onto the cleanup stack before calling a leaving function CleanupStack::PushL(clanger);

clanger->InitializeL(); clanger->DoSomethingElseL()

//Pop from cleanup stack

CleanupStack::Pop(clanger);

delete clanger;

}

If InitializeL() succeeds, the lines of code following the call pop the automatic CClanger pointer from the cleanup stack and delete the object to which it points. This code could equally well be replaced by a single call to CleanupStack::PopAndDestroy(clanger) which performs both the pop and a call to the destructor in one step. The function is useful if the object is no longer required and may also be destroyed. And if InitializeL() or DoSomethingElseL() leave, the clanger object is destroyed by the cleanup stack itself as part of leave processing, which I’ll discuss shortly.

Let’s move on to consider some general good practices when working with the cleanup stack. Objects are pushed on and popped off the cleanup stack in strict order: a series of Pop() calls must occur in the reverse order of the PushL() calls. It is possible to Pop() or PopAndDestroy() one or more objects without naming them, but it’s a good idea to name the object popping off. This averts any potential cleanup stack ”imbalance” bugs which can occur when Pop() removes an object from the cleanup stack which was not the one intended.

void ContrivedExampleL()

{// Note that each object is pushed onto the cleanup stack

//immediately it is allocated, in case the succeeding allocation

//leaves.

CSiamese* sealPoint = NewL(ESeal);

CleanupStack::PushL(sealPoint);

CSiamese* chocolatePoint = NewL(EChocolate);

CleanupStack::PushL(chocolatePoint);

CSiamese* violetPoint = NewL(EViolet);

CleanupStack::PushL(violetPoint);

CSiamese* bluePoint = NewL(EBlue);

CleanupStack::PushL(bluePoint);

USING THE CLEANUP STACK

33

sealPoint->CatchMouseL();

//Other leaving function calls, some of which use the cleanup stack

...

//Various ways to remove the objects from the stack and delete them:

//(1) All with one anonymous call - OK, but potentially risky

//CleanupStack::PopAndDestroy(4);

//(2) All, naming the last object - Better

//CleanupStack::PopAndDestroy(4, sealPoint);

//(3) Each object individually to verify the code logic

//Note the reverse order of Pop() to PushL()

//This is quite long-winded and probably unnecessary in this

//example

CleanupStack::PopAndDestroy(bluePoint);

CleanupStack::PopAndDestroy(violetPoint);

CleanupStack::PopAndDestroy(chocolatePoint);

CleanupStack::PopAndDestroy(sealPoint);

}

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

34

THE CLEANUP STACK

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.

If an object is pushed onto the cleanup stack and remains on it when the function returns, the function name must have the suffix ”C”. This indicates to the caller that, when the function returns successfully, the cleanup stack has additional objects upon it. It is typically used by CBase- derived classes which define static functions to instantiate an instance of the class and leave it on the cleanup stack. The C suffix indicates to the caller of a function that it is not necessary to push any objects allocated by the function onto the cleanup stack. The following code creates an object of type CSiamese (which I used in an earlier example) and leaves it on the cleanup stack. This function is useful because the caller can instantiate CSiamese and immediately call a leaving function, without needing to push the allocated object back onto the cleanup stack:

CSiamese* CSiamese::NewLC(TPointColor aPointColour)

{

CSiamese* me = new (ELeave) CSiamese(aPointColour);

CleanupStack::PushL(me); // Make this leave-safe...

me->ConstructL();

return (me) // Leave "me" on the cleanup stack for caller to Pop()

}

However, a word of warning with regard to functions that leave objects on the cleanup stack – you must take care when calling them from inside a TRAP harness. If objects are pushed onto the cleanup stack inside a TRAP and a leave does not occur, they must be popped off again before