Support

Symbian Developer Network C++ Code Clinic

May 2008: Mixin Inheritance and the Cleanup Stack

Code Clinic

Jo Stichbury opens the code clinic to dispense more advice about Symbian C++.

In this month’s clinic she examines the cause of two panics that occur when the cleanup stack is used with classes that use mixin inheritance.

A new Code Clinic will be published on the first Friday of every month. Got a Symbian C++ problem? Send it to the Code Doctor at sdn@symbian.com.

Problems used will receive a complementary copy of the Symbian Press book Developing Software for Symbian OS, Second Edition.

Dear Code Doctor

I receive a E32USER-CBase 90 when I pop a pointer off the cleanup stack in a debug build. The Symbian Developer Library documentation says the panic occurs "when an the item to be popped is not the expected item" but I can't see how this has happened. I've attached my code. Please can you help to explain what's going wrong?

Mixed up mixin user (Neil, Manchester)

Dear Neil,
Thank you for your question, and for sending your code. I will paste snippets of it here to explain further what the problem is, and how to solve it.

First of all, let's explain what the panic is telling you. An E32USER-CBase 90 panic occurs when you push a pointer onto the cleanup stack and later try to pop it off, using one of the checking overloads of CleanupStack::Pop() or CleanupStack::PopAndDestroy(). These overloads each take a parameter to indicate the pointer you expect to be removing. They panic in debug builds if the pointer you pass in isn't the pointer located on the top of the cleanup stack.

Best practice documentation recommends that you use the checking overloads since they allow you to make sure that your use of the cleanup stack is as you expect it to be. For example, in a complex function that makes heavy use of the cleanup stack, it's handy to use the checking overloads to ensure you've accounted for each pointer pushed to the cleanup stack before the function returns. But in your case, the check appears to be failing without good reason. Here's the code that is causing the problem:

CTestClass* testClass = CTestClass::NewLC();
CleanupStack::Pop(testClass); // E32USER-CBase 90 PANIC! 

OK, that's pretty straightforward. A factory method pushes a pointer onto the cleanup stack and returns. The next line of code pops the pointer off...and panics. Not good!

So what happens inside CTestClass::NewLC()?
/*static*/ CTestClass* CTestClass::NewLC()
    {
    CTestClass* me = new(ELeave) CTestClass();
    CleanupStack::PushL(me);
    return (me);
    }

That's very simple code. It's a static factory method for two-phase construction , but you don't actually call a second-phase constructor. What's more, I've looked at the constructor code for CTestClass() and, quite correctly, it doesn't push anything onto the cleanup stack. So far, there's nothing wrong with your code; you're popping off what was pushed on. So why the panic?

Let's look at your class:

class MCallback
    {
public:
    virtual void OnCallback() = 0;
    };

class CTestClass : public MCallback, public CBase
    {
public:
    static CTestClass* NewLC();
    ~CTestClass() {};
public: // From MCallback
    virtual void OnCallback();
protected:
    CTestClass() {};
    };  

Ah, that's where the problem lies. Look at the line where you specify the inheritance:

class CTestClass : public MCallback, public CBase

The order used puts the mixin class before the CBase class. When using mixin inheritance with C classes on Symbian OS, you should always specify the C class first in the base class list. The solution is simple, change your code as follows:

class CTestClass : public CBase, public MCallback

But why? Well, as the Symbian Developer Library documentation explains, making sure that you always specify the CBase class first means that safe conversions between the C class and TAny* pointers are possible. What this means in practice is that it allows you to avoid panics that occur because code inside Symbian OS assumes that conversion between TAny* and a C class is possible. To keep the following explanation straightforward, I'll gloss over some of the terminology, but I recommend reference [R1] if you are interested in finding out more.

When you call CleanupStack::PushL() in CTestClass::NewLC() you pass the pointer returned when you create CTestClass on the heap. However, because CleanupStack::PushL() has an overload that takes a CBase* pointer, and CTestClass derives from CBase, specified as the second base class, there is an implicit conversion. What this means is that the memory address passed to PushL() is not the start of the CTestClass object, but is incremented by 4 bytes, to the memory address of the CBase 'subobject'. Diagram 1 illustrates this:

The layout of CTestClass

Diagram 1: The layout of CTestClass.

The PushL() method stores the CBase* pointer on the cleanup stack (as a TCleanupItem, as reference [R2] explains). In the above example, it stores the memory location 0x377a2234.

/*static*/ CTestClass* CTestClass::NewLC()
    {
    CTestClass* me = new(ELeave) CTestClass();
    // For example, *me is located at 0x377a2230
    
    CleanupStack::PushL(me);
    // PushL() receives memory location 0x377a2234
    return (me);
    }

When you later call CleanupStack::Pop() to remove the pointer from the cleanup stack, you call the following method (from e32base.inl):

// Class CleanupStack
#ifdef _DEBUG
#ifdef _DEBUG
inline void CleanupStack::Pop(TAny* aExpectedItem)
	{ CleanupStack::Check(aExpectedItem); CleanupStack::Pop(); }

Note that the method takes a TAny* parameter, and passes it to Check(). That method simply compares the memory location stored for the pointer at the top of the cleanup stack with the parameter passed for the expected item. If the two are the same, the check passes, otherwise the E32USER-CBase 90 panic is raised.

The reason your Check() call fails is that the Pop() method takes a TAny* rather than a CBase*. When you call Pop(), no implicit conversion occurs to point to the CBase subobject. The address of the start of the CTestClass object (0x377A2230) is passed instead. The Check() method is compares two different memory locations within the same object, so it fails and panics. Diagram 2 illustrates this.

Layout of CTestClass showing the different memory locations passed to CleanupStack methods

Diagram 2: Layout of CTestClass, showing the different memory locations passed to CleanupStack methods.

If you had called the Pop() method that doesn't perform a check, or if you only ran your code in release build, you would never have seen this panic.

As the Symbian Developer Library points out, the C++ standard does not mandate that object layout follows the order in which base classes are specified, but in practice this is the case for most compilers, including those used for Symbian OS. When using mixin inheritance with C classes on Symbian OS, derive from CBase, or a CBase-derived class first, and then from the mixin class(es), in order to facilitate safe conversion between CBase* and TAny*.

Dear Code Doctor

I read that I should not use CleanupStack::PushL()to push a mixin class pointer to a C class onto the cleanup stack. What should I use if I want to make the pointer leave safe?

Cautious about cleanup (Nicky, Santa Cruz)

Dear Nicky,
You don't mention what you were reading, but it's correct, you should not push mixin class pointers to C classes onto the cleanup stack using CleanupStack::PushL(). Consider what would happen if you did so; which of the these overloads of CleanupStack::PushL() would be called?

static void PushL(TAny* aPtr); 
static void PushL(CBase* aPtr);         // Not this one
static void PushL(TCleanupItem anItem); // Not this one either

Remember that, although your class may also derive from CBase, you have an M class pointer to the object. Your pointer isn't pushed onto the cleanup stack using the CBase* overload - because it isn't one - instead it is pushed to the cleanup stack as a TAny*.

The overload of PushL() used is significant if the cleanup stack is required to destroy the object, either through a call to CleanupStack::PopAndDestroy() or in the event of a leave. When a TAny* object is pushed to the cleanup stack, cleanup occurs by simply invoking a call to User::Free() on the pointer. No destructor calls occur.

However, for your object, that would result in a User 42 panic. This is because the M class pointer isn't at the start of the object (see diagram 3) and User::Free() can't find the heap information it needs to free the memory back to the system. Even if it could, the destructor of the C class would not be called, and this could potentially result in an insidious memory leak. So it's probably just as well a panic occurs to highlight the problem so you can fix it!

Let's look at a rather simplistic example. A C class, CSquareClass, uses multiple inheritance from CBase and an M class interface, MPolygon. An object of CSquareClass is instantiated and the M-class interface pushed onto the cleanup stack, using CleanupDeletePushL() (found in e32base.h). This templated function pushes the pointer to the cleanup stack and ensures that delete is called on it when it is cleaned up. It does this by creating a TCleanupItem item that calls delete on the pointer. You can find out more about this templated function, and others for pushing R classes to the cleanup stack, in reference [R2], and online in the Symbian Developer Library.

class MPolygon
  {
public:
  virtual TInt NumberOfVertices() =0;
  virtual ~MPolygon(){};
  };

class CSquareClass : public CBase, public MPolygon
  {
public:
  static MPolygon* NewL();
  virtual TInt NumberOfVertices(); // From MPolygon
  virtual ~CSquareClass(){};
private:
	CSquareClass (){};
  };

/*static*/ MPolygon* CSquareClass::NewL()
  {// No two-phase construct needed here.
  CSquareClass* me = new(ELeave) CSquareClass();
  return (me);
  }

TInt CSquareClass::NumberOfVertices()
  {// Return number of vertices
  return (4);
  }
To illustrate usage, consider the following:
MPolygon* polygon = CSquareClass::NewL();
CleanupDeletePushL(polygon);
... // Invoke leaving code
CleanupStack::PopAndDestroy(polygon); // OK
MPolygon points at the M class subobject

Diagram 3: MPolygon* points at the M class subobject and not to the start of the CSquareClass object. The object cannot be cleaned up by calling User::Free() on the pointer

As diagram 3 shows, if polygon was simply pushed onto the cleanup stack using CleanupStack::PushL() a USER 42 panic would result. Using CleanupDeletePushL() ensures that cleanup is successful, and the destructor of the C class is invoked. And on this note, finally, notice also that the mixin class has an empty virtual destructor to allow objects that refer to it by M class pointer it to be deleted.

Further Information

References

[R1] Inside the C++ Object Model, Stanley B Lippman, Addison-Wesley, 1996.

[R2] Symbian OS Explained: Effecitive C++ Programming for Smartphones, Jo Stichbury, John Wiley & Sons, 2004.

Contributors

I'd like to thank Vladimir Marko, who provided valuable feedback on aspects of this article, and Hamish Willee and Tanzim Husain from Symbian Developer Network. Thanks also to Neil and Nicky for submitting questions.

The Code Clinic will feature regularly on Symbian Developer Network, on the first Friday of every month. Got a Symbian C++ problem? Send it to the Code Doctor at sdn@symbian.com. Problems used will receive a complementary copy of the Symbian Press book "Developing Software for Symbian OS, Second Edition".

 
[live-web] Terms of use | Privacy policy | Media Center | Contact us | © 2008 Symbian