C ++ "this" does not match the object method called

I came across what seems like a really annoying bug launching my C ++ program under Microsoft Visual C ++ 2003, but it might just be what I'm doing wrong, so I thought I'd throw it away and see if there is Does anyone have any ideas.

I have a class hierarchy like this (just like it is - for example, there is no multiple inheritance in 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, that's why I call "AnnoyingFunctionThatBreaks" an instance of "CMotionWalk" (for example, the debugger says 0x06716fe0), and everything looks good. But when I enter it, to the breakpoint when calling "DoStuff", the 'this' pointer has a different meaning for the pMotion pointer to which I called the method (for example, now the debugger says one word above - 0x06716fe4).

By the way, it is different: pMotion has the value 0x06716fe0, but when I call the method on it, this method sees 'this' as 0x06716fe4.

I'm not going to go crazy, am I? This is weird, right?

+6
c ++ debugging pointers
source share
6 answers

I believe that you just see an artifact of how the compiler creates vtables. I suspect CMotion has its own virtual functions, and therefore you get offsets inside the derived object to get to the base object. Thus, different pointers.

If it works (i.e. if it does not crash and there are no pointers outside the objects), I would not worry too much about it.

+10
source share

Is the CMotion class spawning some other class that contains a virtual function? I found that this pointer does not change with the code you posted, however it changes if you have a 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 due to multiple inheritance for the CMotion class and the vtable pointer in CMotion, which points to Test :: f ()

+6
source share

See also the wikipedia article on thunking . If you install a debugger to pass assembly code, you should see how this happens. (whether it's thunk or just changing the offset depends on the details that you provided from the code you provided)

+2
source share

I think I can explain this ... there is a better explanation somewhere in one of Meyer's or Sutter's books, but I did not want to search. I believe that what you see is a consequence of the implementation of virtual functions (vtables) and "you do not pay for it until you use it," the nature of C ++.

If virtual methods are not used, then the pointer to the object points to the data of the object. Once the virtual method is introduced, the compiler inserts a virtual lookup table (vtable), and instead a pointer points to this. I probably missed something (and my brain is not working yet), since I could not do this until I inserted the data element into the base class. If the base class has a data element and the first child class has a virtual one, then the offsets differ in size vtable (4 in my compiler). Here is an example that clearly shows this:

 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 

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

In your case, once you have created a virtual CWaitable::WakeWaiters , the vtable is inserted and actually draws attention to the real type of the object, as well as the need to build in the necessary bookkeeping. This leads to the fact that the offset base of the object will be different. I really want so that I can find a link that describes a mythical memory layout and why the address of an object depends on the type that it is interpreted when, during inheritance, mixes in pleasure.

General rule: (and you've heard this before) base classes always have virtual destructors. This will help eliminate small surprises like this.

+1
source share

You need to post some actual code. The values ​​for pointers in the following, as expected, that is, 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 ); } 
0
source share

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

0
source share

All Articles