Namespaces

Variants
Actions

Please note that as of October 24, 2014, the Nokia Developer Wiki will no longer be accepting user contributions, including new entries, edits and comments, as we begin transitioning to our new home, in the Windows Phone Development Wiki. We plan to move over the majority of the existing entries over the next few weeks. Thanks for all your past and future contributions.

Fundamentals of Symbian C++/Leaves & The Cleanup Stack

From Wiki
Jump to: navigation, search
Article Metadata
Article
Created: hamishwillee (10 Jan 2011)
Last edited: hamishwillee (23 Jul 2012)

Two of the most fundamental idioms within Symbian C++ are its lightweight exception handling, which is called leaving, and its use of a cleanup stack to manage the destruction of objects and cleanup of resources in the event of an exceptional condition (such as allocation failure due to insufficient memory). A leaving function may be described as one that, when it detects an error, exits at the point of the error and unwinds, performing memory cleanup, until a leave handler is found. This is in contrast to a function that returns an error back to the calling function.

This topic explains the exception handling mechanism, how memory cleanup is managed, and how to write code to avoid memory leaks when a leave occurs.

Contents

Introduction

On desktop software platforms, the implications of a memory leak are not particularly significant; memory is plentiful and any that is leaked gets reclaimed when an application is closed or the system rebooted. It’s important to remember that the Symbian platform was designed to perform well on devices with limited memory that are often not rebooted for days, weeks or even months. Applications are often left running indefinitely rather than closed. Proper use of the cleanup stack will eliminate memory and other resource leaks that could otherwise occur during exception conditions. (See also, a separate article that explains how to eliminate memory leaks in Symbian C++ code).

Symbian C++ was designed before the C++ standard had been formalized, and before exception handling using try, catch or throw was fully supported by compilers. In addition, exception-handling support had the potential to bloat the size of compiled code and add to memory overheads at runtime, so standard C++ exception handling was not used.

The Symbian platform now supports C++ standard exceptions, which makes it easier to port existing C++ code to the Symbian platform. See A Comparison of Leaves and Exceptions. However, leaves are still a fundamental part of Symbian error handling and are used throughout the system. So even if your code uses only standard C++ exceptions, it’s important to know how to blend leaves and exceptions so hybrid code works correctly.

Leaves are implemented in terms of standard C++ exceptions and it is possible to mix leaves and exceptions provided care is taken.

A leave is equivalent to throwing in standard C++ exception handling. Like C++ exceptions, leaves are used to propagate errors to where they can be handled. They should not be used to direct the normal flow of program logic. A leave does not terminate the flow of execution on the Symbian platform (in contrast to panics).

Causes of a Leave

A leave may occur in a function if it:

  • explicitly calls one of the system functions in class User that cause a leave, such as User::Leave() or User::LeaveIfError()
  • uses the Symbian C++ overloaded form of operator new, which takes ELeave as a parameter, and there is insufficient remaining memory to allocate the object
  • calls another function that may leave (for either of the reasons above) without using a TRAP harness.

Let’s now examine each of these ways of leaving in turn.

Functions in Class User That Cause a Leave

The class User is a static utility class, defined in e32std.h. The class provides a set of exported system functions, among which are some that cause a leave in the current thread. The functions are defined as follows:

IMPORT_C static void Leave(TInt aReason);
IMPORT_C static void LeaveNoMemory();
IMPORT_C static TInt LeaveIfError(TInt aReason);
IMPORT_C static TAny* LeaveIfNull(TAny* aPtr);

The various methods:

  • User::Leave() leaves using the integer value passed into it as a leave code.
  • User::LeaveIfError() tests an integer parameter passed into it and calls User::Leave() (passing the integer value as a leave code) if the value is less than zero. (The KErrXXXX error codes defined in e32err.h have negative values, for example KErrNoMemory is -4.) User::LeaveIfError() is useful for turning functions which return error codes (one of the KErrXXXX error constants defined in e32err.h) into functions that leave with the same value. Note that it is poor design to have a function that both leaves and returns an error code.
  • User::LeaveNoMemory()simply leaves with KErrNoMemory; it is equivalent to calling User::Leave(KErrNoMemory).
  • User::LeaveIfNull() takes a pointer and leaves with KErrNoMemory if it is NULL.

Heap Allocation Using new(ELeave)

Symbian C++ overloads the global operator new to leave if there is insufficient heap memory for successful allocation. Use of this overload allows the pointer returned from the allocation to be used without a further test that the allocation was successful, because it would leave if the allocation had failed. For example:

CBook* InitializeBookL()      // The ‘L’ is part of the Symbian C++ naming
// convention and indicates the
// function can leave.
{
CBook* book = new(ELeave) CBook();
book->Initialize(); // No need to test book against NULL.
return (book);
}

The code above is preferable to the following:

CBook* InitializeBookL()
{
CBook* book = new CBook();
User::LeaveIfNull(book);
book->Initialize();
return (book);
}

Or to the following:

CBook* InitializeBook()
{
CBook* book = new CBook();
if (book)
{
book->Initialize();
return (book);
}
else
return (NULL);
}

Leaving is preferable to the above example as it not only simplifies the InitializeBook() method, it also simplifies code that has called this method.

Code That May Leave

How do you know whether a function may leave? As the previous section mentioned briefly, there is a naming convention to indicate the potential for a leave within a function – rather like the C++ throw(...) exception specification.

If a function may leave, its name must end with a trailing L to identify it as such.

Of all Symbian C++ naming conventions, this is probably the most important rule: if a leaving function is not named correctly, callers of that function cannot know that they need to take steps to handle a potential exception. If a leave occurs and it is not TRAPed, a panic will result.

Since it is not part of the C++ standard, the trailing L cannot be checked during compilation, and can sometimes be forgotten. The Symbian Foundation provides a helpful tool, LeaveScan to check code for incorrectly named leaving functions. Other source code scanning tools (e.g. like "Coverty Prevent") can also be used for this purpose: information on tools can be found in the Tools category.

Leaving functions should not also return an error code. Any error that occurs in a leaving function should be passed out through the leave code; if the function does not leave, it is deemed to have succeeded and will return normally. Generally, leaving functions should return void unless they return a pointer or reference to a resource that they have allocated.

The TRAP, TRAPD, and TRAP_IGNORE Macros

The TRAP and TRAPD macros are harnesses that trap leaves and allow them to be handled:

TRAPD(result, MayLeaveL());


If a leave occurs inside MayLeaveL(), the stack is unwound, any memory on the cleanup stack is freed, and control will return to just after the TRAP harness macro. The result variable then contains the error code associated with the leave (or KErrNone if no leave occurred).

Any functions called by MayLeaveL() are executed within the context of the TRAP harness. Any leave occurring during the execution of MayLeaveL() is trapped regardless of how many calls deep in the call stack it happens.

TRAP/TRAPD are therefore approximately equivalent to the catch {} of C++ standard exception handling.

The TRAP and TRAPD macros differ only in that TRAPD declares the variable in which the leave error code is returned, while code using TRAP must declare a TInt variable itself first. Thus the following code segments are equivalent:

TRAPD(result, MayLeaveL());
if (KErrNone!=result)
{
// Handle error.
}
 
TInt result;
TRAP(result, MayLeaveL());
 
if (KErrNone!=result)
{
// Handle error.
}

TRAP macros can also be nested to catch and handle leaves at different levels, where they can best be dealt with. However, each TRAP has an impact on executable size and execution speed, so the number of TRAPs should be minimized where possible. For example, suppose that the following function needs to call a number of functions that leave, but must not leave itself. It might seem straightforward simply to put each call in a TRAP harness:

TInt NonLeavingFunction()
{
TRAPD(result, MayLeaveL());
if (KErrNone==result)
TRAP(result, MayAlsoLeaveL());
if (KErrNone==result)
TRAP(result, MayBeALeaverL());
 
// Return any error or KErrNone if successful.
return (result);
}

The following refactoring is preferable for efficiency:

TInt NonLeavingFunction()
{
TRAPD(result, LeavingFunctionL());
 
// Return any error or KErrNone if successful.
return (result);
}
 
void LeavingFunctionL()
{
MayLeaveL();
MayAlsoLeaveL();
MayBeALeaverL();
}

If you have no need to take any action (or even be notified that a Leave occurred!), you may use the TRAP_IGNORE macro instead. This harness is equivalent to a TRAPD() that isn't followed by any code to evaluate its result parameter:

TRAP_IGNORE(MayLeaveL());

What Causes Memory Leaks?

If memory is allocated on the heap and referenced only by a stack-based local variable then the memory will be leaked in the event of a leave.

This happens because when you create a local pointer to a heap-based variable the pointer resides on the stack. When a leave occurs, the stack is restored to (a copy) of the stack frame as it was at the time the TRAP harness was first called. The heap object is orphaned – it still exists on the heap, but the stack pointer has been discarded. As the memory is unrecoverable, this is a memory leak.

The following code illustrates this:

void UnsafeL()
{
 
// Heap allocation.
CBook* book = new(ELeave) CBook();
 
book->InitializeL(); // Unsafe - book will be leaked if
 
// InitializeL() leaves
delete book;
}

The memory allocated on the heap to store the CBook object pointed to by book will become inaccessible if the call to InitializeL() leaves. The cleanup stack must be used to prevent this, as the next section explains.

Note that the argument above does not apply to member variables because the pointer to the member is maintained in the parent object rather than the stack. You should never put a member variable on the cleanup stack for the reasons discussed in the following section.

The Cleanup Stack

The previous section explained that a memory leak can occur if heap objects are accessible only through pointers local to the function that leaves. Let’s look at this in terms of the Symbian C++ types that were discussed here.

  • T class objects are leave safe – they generally do not contain any member data that requires explicit destruction; if they have a destructor defined, it gets called when the object goes out of scope (the nature of T class objects has changed as Symbian C++ has evolved, here and The Implications of Leaving in a Destructor for further details).
  • C class objects, which are always created on the heap, are not leave safe if created as a local variable but are leave safe if created as a member variable of another class (which in turn, by definition, must be another C class).
  • R class objects are similarly not leave safe if created as a local variable, since the resources they own must be freed in the event of a leave (through a call to the appropriate Close() or Release() function).
  • M class objects may or may not be leave safe, depending on the type of class the interface has been mixed in to. To better understand this point, see the MPolygon example later.

One way to make objects leave safe is to place a TRAP (or TRAPD) macro around every potential leaving call. However, the use of TRAPs should be limited for reasons of efficiency, and to avoid clumsy code that constantly traps leaves and checks leave codes. In effect, code that uses multiple trap harnesses to catch every possible leave is simply reverting to the basic error-handling approach that leaves were designed to avoid. Instead, Symbian C++ provides a mechanism for storing pointers that are not leave safe, to ensure they are cleaned up in the event of a leave. This is called the cleanup stack. Each thread has its own cleanup stack and, as long as pointers are stored on it before calling code that may leave, the cleanup stack destroys them automatically. The cleanup stack is rather like a Symbian version of the standard C++ library’s smart pointer, auto_ptr.

It isn’t necessary to create a cleanup stack for a GUI application thread, since the application framework creates one. However, a cleanup stack must be created if writing a server, a simple console-test application or when creating an additional thread that contains code that uses the cleanup stack. This is done as follows:

CTrapCleanup* theCleanupStack = CTrapCleanup::New();
 
... // Code that uses the cleanup stack within a TRAP macro.
 
delete theCleanupStack;

Once created, the cleanup stack 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);
 
};

Using the Cleanup Stack

The following code illustrates a leave-safe version of the function UnsafeL() discussed earlier:

void NowSafeL()
{
 
// Heap allocation.
CBook* book = new(ELeave) CBook();
 
// Push on to the cleanup stack.
 
CleanupStack::PushL(book);
book->InitializeL(); // Safe
 
// Pop from the cleanup stack.
 
CleanupStack::Pop(book);
 
delete book;
}

If InitializeL() leaves, the object pointed to by book is destroyed by the cleanup stack. If no leave occurs, the book pointer is popped from the cleanup stack and the object to which it points is deleted explicitly when finished with. This code could equally well be replaced by a call to CleanupStack::PopAndDestroy(book) that pops the pointer and makes a call to the destructor in one step.

In the example above, you may have noticed that PushL() is a leaving function, which at first sight appears to be self-defeating. If the cleanup stack is supposed to make pointers leave safe, how can it potentially cause a leave when you use it and still be helpful? The reason PushL() may leave is that it may need to allocate memory to store the pointers passed to it (and in low-memory conditions this could fail). However, the object passed to PushL()will not be orphaned if it leaves. This is because, when the cleanup stack is created, it has at least one spare slot. When PushL() is called, the pointer is added to the next vacant slot and then, if there are no remaining slots available, the cleanup stack attempts to allocate some for future pushes – it is this allocation that may fail. If there is a failure, the pointer has already been added to the stack so its object can be cleaned up.

The cleanup stack is expanded four slots at a time for efficiency, and Pop() and PopAndDestroy() do not release the memory for slots once they have been allocated. This memory is available for future pushes on to the cleanup stack until the cleanup stack is destroyed.

In a function, if a pointer to an object is pushed on to the cleanup stack and remains on it when that function returns, the naming convention is to append a C to the function name. For example:

CBook* NowSafeLC()
{
// Heap allocation.
CBook* book = new(ELeave) CBook();
 
// Push on to the cleanup stack.
CleanupStack::PushL(book);
 
book->InitializeL();
 
return (book);
}

This is used most frequently in static factory functions for C classes.

Ordering Calls to PushL() and Pop() or PopAndDestroy()

Pointers can be pushed on to and popped off the cleanup stack, but since it’s a stack, for any series of pushes, the corresponding pops must occur in reverse order. It’s a good idea to name the pointer as it is popped, so debug builds can check that it is the correct pointer and flag up any programming errors. For example:

void TestPhonesL()
{ // Each object is pushed on to the cleanup stack immediately
// it is allocated, in case the next allocation leaves.
 
CPhone* phoneA = CPhone::NewL(A);
CleanupStack::PushL(phoneA);
 
CPhone* phoneB = CPhone::NewL(B);
CleanupStack::PushL(phoneB);
 
CPhone* phoneC = CPhone::NewL(C);
CleanupStack::PushL(phoneC);
 
CPhone* phoneD = CPhone::NewL(D);
CleanupStack::PushL(phoneD);
 
... // Leaving functions called here.
 
// Various ways to remove the objects from the stack and delete them:
 
// (1) All with one anonymous call – OK
// CleanupStack::PopAndDestroy(4);
 
// (2) Each object individually to verify the code logic
// Note the reverse order of Pop() to PushL().
 
// This is long-winded.
// CleanupStack::PopAndDestroy(phoneD);
// CleanupStack::PopAndDestroy(phoneC);
// CleanupStack::PopAndDestroy(phoneB);
// CleanupStack::PopAndDestroy(phoneA);
 
// (3) All at once, naming the last object. This is the best solution.
CleanupStack::PopAndDestroy(4, phoneA);
 
}

Using the Cleanup Stack with T, R and M Classes

As you can see from the definition of class CleanupStack given earlier, there are three overloads of the PushL() method. These determine how the item is destroyed when it is cleaned up when a leave occurs or a call is made to CleanupStack::PopAndDestroy().

IMPORT_C static void PushL(CBase* aPtr);

This overload takes a pointer to a CBase-derived object. On cleanup it is destroyed by invoking delete, thus calling the virtual destructor of the CBase-derived object.

IMPORT_C static void PushL(TAny* aPtr);

If the object pushed on to the cleanup stack is a pointer to an object that does not derive from CBase, this overload is invoked. On cleanup, the memory referenced by the pointer is simply deallocated by invoking User::Free() (delete is not called on it, so no destructor is invoked).

This overload of PushL() is called, for example, when pointers to heap-based T class objects are pushed on to the cleanup stack. If the T class has no member data that requires explicit de-allocation this is fine because there is no requirement for cleanup beyond de-allocation of the heap memory occupied by the T class. However, if the T class has a destructor that requires calling this overload of PushL() will not suffice, instead a TCleanupItem should be used.

IMPORT_C static void PushL(TCleanupItem anItem);

This overload takes an object of type TCleanupItem, which is designed to accommodate objects requiring customized cleanup processing. The TCleanupItem object encapsulates a pointer to the object to be stored on the cleanup stack and a pointer to a function to clean up that object (a local function or a static method of a class). A leave or a call to PopAndDestroy() removes the object from the cleanup stack and calls the associated cleanup function.

Symbian C++ also provides a set of template utility functions, each of which generates an object of type TCleanupItem and pushes it on to the cleanup stack:

  • CleanupClosePushL() – the cleanup method calls Close() on the object in question. This utility method is usually used to make stack-based R class objects leave safe. For example:
void UseFilesystemL()
{
 
RFs theFs;
User::LeaveIfError(theFs.Connect());
 
CleanupClosePushL(theFs);
... // Call functions which may leave,
// theFs.Close() is called if a leave occurs.
 
CleanupStack::PopAndDestroy(&theFs); // Note the & is required
}
  • CleanupReleasePushL() – the cleanup method calls Release() on the object in question. This method is typically used to push an object referenced through an M class (interface) pointer that provides a Release() method.
  • CleanupDeletePushL() – the cleanup method calls delete on the pointer passed into the function. This is also typically used for passing M class pointers.

The following simplistic example illustrates the reason why this is needed (for those interested, a longer discussion can be found on our page about mixin inheritance and the cleanup stack.

A C class, CSquare, uses multiple inheritance from CBase and an M class interface, MPolygon. The object is instantiated and the M class interface pushed on to the cleanup stack. The object is later destroyed by a call to PopAndDestroy(). If the pushing was done with CleanupStack::PushL(), the PushL(TAny*) would be used because the pointer is not a CBase pointer. The PushL(TAny*) overload means that User::Free() is invoked on the pointer on cleanup, but this would result in a User 42 panic. That’s because User::Free() can't find the heap information it needs to release the memory to the system when passed an M class pointer to the sub-object of a C class. Even if it could, the virtual destructor of CSquare would not be called, so the best-case scenario would be a memory leak! The solution is to use CleanupDeleteL() to call delete on the M class pointer and invoke the virtual destructor correctly for the CSquare class.

class MPolygon
{
 
public:
 
virtual TInt CalculateNumberOfVerticesL() =0;
virtual ~MPolygon(){};
 
};
 
class CSquare : public CBase, public MPolygon
{
public:
static CSquare* NewL();
virtual TInt CalculateNumberOfVerticesL();
virtual ~CSquare(){};
 
private:
CSquare(){};
 
};
 
enum TPolygonType { ESquare };
 
static MPolygon* InstantiatePolygonL(TPolygonType aPolygon);
 
MPolygon* InstantiatePolygonL(TPolygonType aPolygon)
{
if (ESquare==aPolygon)
return (CSquare::NewL());
else
return (NULL);
}
 
/*static*/ CSquare* CSquare::NewL()
{ // No two-phase construct needed here.
CSquare* me = new(ELeave) CSquare();
return (me);
}
 
TInt CSquare::CalculateNumberOfVerticesL()
{ // Simple implementation, but others may leave.
return (4);
}
 
void DoStuffL()
{
MPolygon* square = InstantiatePolygonL(ESquare);
CleanupDeletePushL(square);
 
// ... Leaving calls go here.
ASSERT(4==square->CalculateNumberOfVerticesL());
CleanupStack::PopAndDestroy(square);
}
  • CleanupArrayDeletePushL() – this method is used to push a pointer to a heap-based C++ array of T class objects (or built-in types) on to the cleanup stack. When PopAndDestroy() is called, the memory allocated for the array is cleaned up using delete[]. No destructor is called on the elements of the array.

Member Variables and the Cleanup Stack

A class member variable (prefixed by i) should not be pushed on to the cleanup stack. The owning object destroys it when appropriate, so it does not need to be made leave safe through use of the cleanup stack.

Note that if you do add a member variable to the CleanupStack, there will be a panic in the event of a leave. This is because both the cleanup stack and the parent object will try and clean up the memory, resulting in a double deletion exception.

Mixing Leaves, Exceptions and the Cleanup Stack

Symbian C++ TRAPs and leaves are implemented internally in terms of C++ exceptions (a leave is an XLeaveException). A TRAP will panic if it catches any other kind of exception. This means that, if you are mixing C++ exceptions in with leaves and TRAPs, you must not throw an exception within a leaving function unless also you catch and handle it internally in that function.

A normal catch block will not manage the cleanup stack correctly either, so code that throws exceptions should not use the cleanup stack directly or indirectly. If you are calling leaving functions from exception-style code, do not use the cleanup stack, but make the calls as required and catch and handle any exceptions that arise.

Memory-Management Macros

Symbian OS provides a set of debug-only macros that can be added directly to code to check that memory is not leaked.

There are a number of macros available, but the most commonly used are defined as follows:

#define __UHEAP_MARK User::__DbgMarkStart(RHeap::EUser)
#define __UHEAP_MARKEND User::__DbgMarkEnd(RHeap::EUser,0)

The macros verify that the default user heap is consistent. The check starts after a call to __UHEAP_MARK and a later call to __UHEAP_MARKEND verifies that the heap is in the same state as at the start. If heap cells allocated in the interim have not been freed, a panic is raised to indicate a potential leak. The panic is ALLOC nnnnnnnn, where nnnnnnnn is a hexadecimal pointer to the heap cell in question.

The heap-checking macros can be nested used anywhere in code. They are not compiled into release builds so do not have any impact on the code size or speed of production code. Sometimes you may find that you get false positives where memory is allocated by the system and not freed within a MARK/MARKEND pair. A good example is the cleanup stack which is expanded to store pointer values where necessary, but when they are popped off, the allocated memory is retained and re-used later or destroyed when the cleanup stack is deleted.

In addition, an application must gracefully handle any out-of-memory leaves that occur when memory is not available for allocation. Symbian OS provides a set of macros to simulate heap failure, either on the next allocation, randomly or by allowing you to specify the number of allocations that can succeed before failure. These are extremely useful: for example, a test suite can call a function repeatedly, increasing the heap failure point by one, to check that each allocation fails gracefully. Please consult the Symbian Developer Library documentation for more information about __UHEAP_FAILNEXT and __UHEAP_SETFAIL for more information about how to use them.

See also


Licence icon cc-by-sa 3.0-88x31.png© 2010 Symbian Foundation Limited. This document is licensed under the Creative Commons Attribution-Share Alike 2.0 license. See http://creativecommons.org/licenses/by-sa/2.0/legalcode for the full terms of the license.
Note that this content was originally hosted on the Symbian Foundation developer wiki.

This page was last modified on 23 July 2012, at 07:46.
359 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×