none
Adjuster Thunks

    Question

  •  

    Take the following code

    class A
    {
    public:
        A()
        {
         a = 0;
        }
        virtual void Print()
        {
         cout<<" a = "<<a<<endl;
        }
    protected:
           int a;

    };

    //***************************************************************************

    class B
    {
    public:
     B()
     {
      b = 0;
     }
     virtual void Check()
     {
             cout<<" This is Check() of class B"<<endl;
     }

    protected:
           int b;
       
    };

    //***************************************************************************

    class C : public A,public B
    {
    public:
        C()
        {
         c = 0;
        }
        virtual void Print()
        {
       cout<<" c = "<<c<<endl;
        }
        virtual void Check()
      {
         cout<<" This is Check() of class C"<<endl;
      }
    private:
         int c;
    };

    //***************************************************************************

    void main()
    {
     C obj;

     A* aPtr = &obj;
     aPtr->Print();
     B* bPtr = &obj;
     bPtr->Check();
     
    }

     

    The class C overrides the virtual function from class A  [ Print() ] and the virutal function from class B [ Check() ].

    If I create a object for class C [ say obj] the entry point is same for A & C in obj.

    But where the entry point for B in obj is quite different ie., the expression (B*)obj does not point to the same part of the class as does (A*)obj.

    Since the function C::Check() expects to receive an C* as its hidden this parameter, we should have an adjuster thunk in the vftable of B class in the C object.

    But there is no such entry available ib the vftable of B class in the C object why?

    Then how the mechanism of converting the B* to C* is achieved?

     

     

    Friday, September 14, 2007 6:11 AM

Answers

  • C::Check does need an this-ptr adjustor thunk and the compiler does provide one:

     

    Code Snippet

    class A size(8):
     +---
     0 | {vfptr}
     4 | a
     +---

    A::$vftable@:
     | &A_meta
     |  0
     0 | &A::Print

    A::Print this adjustor: 0


    class B size(8):
     +---
     0 | {vfptr}
     4 | b
     +---

    B::$vftable@:
     | &B_meta
     |  0
     0 | &B::Check

    B::Check this adjustor: 0


    class C size(20):
     +---
     | +--- (base class A)
     0 | | {vfptr}
     4 | | a
     | +---
     | +--- (base class B)
     8 | | {vfptr}
    12 | | b
     | +---
    16 | c
     +---

    C::$vftable@A@:
     | &C_meta
     |  0
     0 | &C::Print

    C::$vftable@B@:
     | -8
     0 | &C::Check

    C::Print this adjustor: 0
    C::Check this adjustor: 8

     

     

     

    Friday, September 14, 2007 6:17 PM

All replies

  • I'm not sure I completely understand what you mean. There's no need to convert a B* to a C*. The compiler knows that C::Check will either be called by a C object / reference / pointer or a B reference / pointer, and address all (inherited) C instance members thereafter. The instance pointer will target the same location within the C object no matter how Check is called -- granted that the call isn't malformed by casts or similar. Since the instance pointer remains the same, no offsets will need adjustment.
    Friday, September 14, 2007 11:51 AM
  • C::Check does need an this-ptr adjustor thunk and the compiler does provide one:

     

    Code Snippet

    class A size(8):
     +---
     0 | {vfptr}
     4 | a
     +---

    A::$vftable@:
     | &A_meta
     |  0
     0 | &A::Print

    A::Print this adjustor: 0


    class B size(8):
     +---
     0 | {vfptr}
     4 | b
     +---

    B::$vftable@:
     | &B_meta
     |  0
     0 | &B::Check

    B::Check this adjustor: 0


    class C size(20):
     +---
     | +--- (base class A)
     0 | | {vfptr}
     4 | | a
     | +---
     | +--- (base class B)
     8 | | {vfptr}
    12 | | b
     | +---
    16 | c
     +---

    C::$vftable@A@:
     | &C_meta
     |  0
     0 | &C::Print

    C::$vftable@B@:
     | -8
     0 | &C::Check

    C::Print this adjustor: 0
    C::Check this adjustor: 8

     

     

     

    Friday, September 14, 2007 6:17 PM
  • I was under the impression that the "adjustor thunk" would imply a jump table or offset based on the polymorphic type of the instance pointer, as opposed to the fact that the instance is offset into C, targetting B's base. (The latter of which I tried to explain.)

    My bad Smile
    Friday, September 14, 2007 6:25 PM
  • Reconsidering this it occurs to me that the thunk probably refers to the inlined piece of code that adjusts the instance pointer assignment. For some reason my notion of 'thunk' has been a separate code block, such as the vcalls.
    Friday, September 14, 2007 6:31 PM
  •  

    Hi Jonathan

    Why I didnt get adjuster thunk in my vtable entry?

    Is it compiler dependent?

    I'm using visual studio 6.0

    Do we have some setting to do this? [ like we have pragma vtordisp to swithc ON and OFF the vtordisp entry ]

    I thought as you are with vc++ compiler team it is more appropriate to ask you.

    Monday, September 17, 2007 11:53 AM
  • First off my usual comment - Visual C++ 6.0 is no longer supported.

     

    I would be surprised that you don't have an adjustor thunk. One way to test this is to call a virtual method which attempts to write out the value of some data member. If you don't have a adjustor thunk then the values should be garbage - if they are valid then you are getting an adjustor thunk.

     

    Monday, September 17, 2007 6:03 PM
  • class A
    {
    public:
        A()
        {
         a = 0;
        }
        virtual void Check()
      {
             cout<<" This is Check() of class A"<<endl;
      }
    protected:
           int a;

    };

    class B
    {
    public:
     B()
     {
      b = 0;
     }
     virtual void Print()
     {
        cout<<" b = "<<b<<endl;
     }

    protected:
           int b;
       
    };

    class C : public A,public B
    {
    public:
        C()
        {
         c = 100;
        }
        virtual void Check()
      {
         cout<<" This is Check() of class C"<<endl;
      }
        virtual void Print()
        {
       cout<<" c = "<<c<<endl;
        }
       
    private:
         int c;
    };

    void main()
    {
     C obj;
     B* bPtr = &obj;
     bPtr->Print();

    }

    As you said i called the virtual function which prints the member values.But it works fine.In case of the call bPtr->Print() it calls the version C:Stick out tonguerint() and prints the c. But when i watch the variable obj i dont see any adjuster thunk entry.

    I tried in VS 2005 also but I'm getting the same results.

     

    Tuesday, September 18, 2007 4:03 AM
  •  Syed Babu wrote:

     

    [...]

    Since the function C::Check() expects to receive an C* as its hidden this parameter, we should have an adjuster thunk in the vftable of B class in the C object.

    But there is no such entry available ib the vftable of B class in the C object why?

    Then how the mechanism of converting the B* to C* is achieved?

     

     

    I do not think C::Check expects a “this” pointer pointing to C object. It seems the “this” pointer is always a B object inside C::Check. In order to check it, add a line like this into C::Check:

    const C * ptrC = this;

    Then investigate the generated Assembler code (use an C/C++ à Output Files compiler option).

    Also see how Check is called for C objects:

    C obj;

    obj.Check();

    The Assembler code will include conversion from C * to B *.

     

    I hope this makes sense.

     

    Tuesday, September 18, 2007 11:52 AM
  •  Viorel. wrote:

    The Assembler code will include conversion from C * to B *.

    I was under the refined impression that this is what's called the adjustor thunk.

    Tuesday, September 18, 2007 12:13 PM
  • Things are not quite as you are assuming - in fact they are, to some extent, the reverse of what you are assuming. As C:: Print overrides B:: Print it is compiled to assume that it always called with pointer to a B.

     

    You can see this if you look at the class-layout of C:

    Code Snippet
    class C size(20):
            +---
            | +--- (base class A)
     0      | | {vfptr}
     4      | | m_a
            | +---
            | +--- (base class B)
     8      | | {vfptr}
    12      | | m_b
            | +---
    16      | m_c
            +---

     

     

    And then look at the disassembly for C:: Print (note to make things clearer I switched to using printf):

    Code Snippet

    ; 51   :   printf("In: C::vmf2(): m_b = %d: m_c = %d\n", m_b, m_c);

      00007 8b 45 fc  mov   eax, DWORD PTR _this$[ebp]
      0000a 8b 48 08  mov   ecx, DWORD PTR [eax+8]
      0000d 51        push  ecx
      0000e 8b 55 fc  mov   edx, DWORD PTR _this$[ebp]
      00011 8b 42 04  mov   eax, DWORD PTR [edx+4]
      00014 50        push  eax

     

     

    See how m_b is at this+4 and m_c is at this+8 (remember as printf is a __cdecl function the arguments are pushed right to left so m_c is before m_b). These offsets are both relative to a B*.

     

    So when C:: Print is called with B* no thunk is required - but if we call it with a C* then we do need a thunk: so if we look at the disassembly for this function:

    Code Snippet

     

    void f2(C* pC)

    {

       pC->Print();

    }

     

     

    Then we will see the following:

    Code Snippet

    ; 65   :  pC->Print();

      00023 8b 4d 08  mov   ecx, DWORD PTR _pC$[ebp]
      00026 83 c1 08  add   ecx, 8
      00029 8b 45 08  mov   eax, DWORD PTR _pC$[ebp]
      0002c 8b 50 08  mov   edx, DWORD PTR [eax+8]
      0002f 8b 02     mov   eax, DWORD PTR [edx]
      00031 ff d0     call  eax

     

     

    The second instruction is the this-pointer adjustment - it changes the this-pointer from a C* to a B* - which is what C:: Print expects.

     

    This all deeply buried in the C++ compiler and it was all set out in stone back in the early 1990's - before I even joined the Visual C++ compiler team.
    Tuesday, September 18, 2007 4:55 PM
  •  

    I'm not still very clear about this.

     

    1. If i call the Print function via B* , the pointer has to be converted to C*. Am i right?

    2. If so how the conversion is happening with out adjuster thunk?

     

    If i call the print function via C* as you said we need a adjuster thunk i agree.

    3. But there is no adjuster thunk entry in the object lay out at all. Then how the call is getting succeeded?

     

     

    Wednesday, September 19, 2007 7:28 AM
  • 1) No you are not correct - see my comments above C:Stick out tonguerint is compiled assuming it alwasy called with a pointer to B*

    2) N/A ... see 1) above

    3) There is - see the generated assembly above. The thunk is 'inlined' as all it does is adjust the this-pointer.

     

    Wednesday, September 19, 2007 4:04 PM