views:

398

answers:

6

I have come across what seems like a really annoying bug running my C++ program under Microsoft Visual C++ 2003, but it could just be something I'm doing wrong so thought I'd throw it out here and see if anybody has any ideas.

I have a hierarchy of classes like this (exactly as is - e.g. there is no multiple inheritance in the real code):

class CWaitable
{
public:
    void WakeWaiters() const
    {
        CDifferentClass::Get()->DoStuff(this);  // Breakpoint here
    }
};

class CMotion : public CWaitable
{
   virtual void NotUsedInThisExampleButPertinentBecauseItsVirtual() { }
};

class CMotionWalk : public CMotion
{ ... };

void AnnoyingFunctionThatBreaks(CMotion* pMotion)
{
    pMotion->WakeWaiters();
}

Okay, so I call "AnnoyingFunctionThatBreaks" with a "CMotionWalk" instance (e.g. the debugger says it's 0x06716fe0), and all seems well. But when I step into it, to the breakpoint on the call to "DoStuff", the 'this' pointer has a different value to the pMotion pointer I called the method on (e.g. now the debugger says one word higher - 0x06716fe4).

To phrase it differently: pMotion has the value 0x06716fe0, but when I call a method on it, that method sees 'this' as 0x06716fe4.

I'm not just going mad am I? That is weird, right?

+2  A: 

See also the wikipedia article on thunking. If you set the debugger to step through the assembly code, you should see it happening. (whether it's a thunk or simply changing the offset depends on details you've elided from the code you give)

Pete Kirkham
I tried stepping through the assembly code. I don't really know what I'm looking for, but it didn't look that unusual
andygeers
A: 

You need to post some actual code. The values for the pointers in the following are as expected - i.e. they are the same:

#include <iostream>
using namespace std;

struct A {
    char x[100];
    void pt() {
     cout << "In A::pt this = " << this << endl;
    }
};

struct B : public A { 
    char z[100];
};

void f( A * a ) {
    cout << "In f ptr = " << a << endl;
    a->pt();
}

int main() {
    B b;
    f( &b );
}
anon
+6  A: 

Is CMotion class is deriving some other class also which contains a virtual function? I found that the this pointer does not change with the code you posted, however it changes if you have the hierarchy something like this:

class Test
{
public:
    virtual void f()
    {

    }
};

class CWaitable 
{
public:
    void WakeWaiters() const
    {
        const CWaitable* p = this;
    }
};

class CMotion : public CWaitable, Test
{ };


class CMotionWalk : public CMotion
{
public:
 };



void AnnoyingFunctionThatBreaks(CMotion* pMotion)
{
    pMotion->WakeWaiters();
}

I believe this is because of the multiple inheritance for CMotion class and the vtable pointer in CMotion which points to Test::f()

Naveen
There's definitely not any multiple inheritance.
andygeers
+10  A: 

I believe you are simply seeing an artifact of the way that the compiler is building the vtables. I suspect that CMotion has virtual functions of it's own, and thus you end up with offsets within the derived object to get to the base object. Thus, different pointers.

If it's working (i.e. if this isn't producing crashes, and there are no pointers outside the objects) then I wouldn't worry about it too much.

Michael Kohne
Well, it's not working, because the DoStuff method gets confused if "this" isn't the same as what it's seen before. But you're exactly spot on with your explanation - I'll add it to the question, that CMotion has virtual functions, so declaring CWaitable::WakeWaiters as virtual solves the problem.
andygeers
i don't understand it how the offset could change. the derived class has a base class and both of them start at the same spot in memory. why on earth should the offset be different without multiple inheritance? does msvc do something strange?
Johannes Schaub - litb
I have no idea how MSVC sets things up in memory, but if you think about it, there has to be somewhere within the Derived object where the Base object starts. It's possible to put the derived elements either BEFORE or AFTER the base elements, depending on the whim of the compiler.
Michael Kohne
A: 

I can't explain why this works, but declaring CWaitable::WakeWaiters as virtual fixes the issue

andygeers
+1  A: 

I think that I can explain this one... there is a better explanation somewhere in one of either Meyer's or Sutter's books, but I didn't feel like searching. I believe what you are seeing is a consequence of how virtual functions are implemented (vtables) and the "you don't pay for it until you use it" nature of C++.

If there are no virtual methods in use, then a pointer to the object points to the object's data. As soon as a virtual method is introduced, the compiler inserts a virtual lookup table (vtable) and the pointer points to this instead. I'm probably missing something (and my brain isn't working yet) since I could not get this to occur until I inserted a data member in the base class. If the base class has a data member and the first child class has a virtual, then the offsets differ by the size of the vtable (4 on my compiler). Here's an example that shows this clearly:

template <typename T>
void displayAddress(char const* meth, T const* ptr) {
    std::printf("%s - this = %08lx\n", static_cast<unsigned long>(ptr));
    std::printf("%s - typeid(T).name() %s\n", typeid(T).name());
    std::printf("%s - typeid(*ptr).name() %s\n", typeid(*ptr).name());
}

struct A {
    char byte;
    void f() { displayAddress("A::f", this); }
};
struct B: A {
    virtual void v() { displayAddress("B::v", this); }
    virtual void x() { displayAddress("B::x", this); }
};
struct C: B {
    virtual void v() { displayAddress("C::v", this); }
};

int main() {
   A aObj;
   B bObj;
   C cObj;

   std::printf("aObj:\n");
   aObj.f();

   std::printf("\nbObj:\n");
   bObj.f();
   bObj.v();
   bObj.x();

   std::printf("\ncObj:\n");
   cObj.f();
   cObj.v();
   cObj.x();

   return 0;
}

Running this on my machine (MacBook Pro) prints the following:

aObj:
A::f - this = bffff93f
A::f - typeid(T)::name() = 1A
A::f - typeid(*ptr)::name() = 1A

bObj:
A::f - this = bffff938
A::f - typeid(T)::name() = 1A
A::f - typeid(*ptr)::name() = 1A
B::v - this = bffff934
B::v - typeid(T)::name() = 1B
B::v - typeid(*ptr)::name() = 1B
B::x - this = bffff934
B::x - typeid(T)::name() = 1B
B::x - typeid(*ptr)::name() = 1B

cObj:
A::f - this = bffff930
A::f - typeid(T)::name() = 1A
A::f - typeid(*ptr)::name() = 1A
C::v - this = bffff92c
C::v - typeid(T)::name() = 1C
C::v - typeid(*ptr)::name() = 1C
B::x - this = bffff92c
B::x - typeid(T)::name() = 1B
B::x - typeid(*ptr)::name() = 1C

The interesting thing is that both bObj and cObj exhibit the address change as between calling methods on A and B or C. The difference is that B contains a virtual method. This allows the compiler to insert the additional table necessary to implement function virtualization. The other interesting thing that this program shows is that typeid(T) and typeid(*ptr) is different in B::x when it is called virtually. You can also see a size increase using sizeof as soon as the virtual table is inserted.

In your case, as soon as you made CWaitable::WakeWaiters virtual, the vtable is inserted and it actually pays attention to real type of the object as well as inserting the bookkeeping structures necessary. This causes the offset to the base of the object to differ. I really wish that I could find the reference that describes a mythical memory layout and why the address of an object is dependent on the type that it is being interpreted as when inheritance is mixed into the fun.

General Rule: (and you have heard this before) base classes always have virtual destructors. This will help eliminate little surprises like this.

D.Shawley
The idea is that each class has its data stored in a contiguous block of memory and its methods are generated such that the access to that data is as fast as possible. Therefore the "this" pointer for those methods don't have to change within the same context.
Tommy Hui