2

Consider the following code with 3-level multiple inheritance hierachy.

auto addr = [](auto v) -> uint64_t { return *reinterpret_cast<uint64_t*>(v); };

struct BaseA
{
    void virtual a() {}
};

struct BaseB
{
    void virtual b() {}
};

struct BaseC : BaseA, BaseB
{
    void virtual a() override {}
    void virtual b() override {}
};

struct BaseD
{
    void virtual d() {}
};

struct BaseE : BaseD, BaseC
{
    void virtual d() override {}
    void virtual a() override {}
    void virtual b() override { auto a = this; std::cout << "called here: " << addr(&a) << "\n"; }
};

int main()
{

    BaseE obj;

    BaseE* ePtr = &obj;
    BaseD* dPtr = &obj;
    BaseC* cPtr = &obj;
    BaseB* bPtr = &obj;
    BaseA* aPtr = &obj;

    ePtr->b();
    cPtr->b();
    bPtr->b();

    std::cout << "e is at: " << addr(&ePtr) << "\n"
              << "d is at: " << addr(&dPtr) << "\n"
              << "a is at: " << addr(&aPtr) << "\n"
              << "c is at: " << addr(&cPtr) << "\n"
              << "b is at: " << addr(&bPtr) << "\n"
              << "total size is " << sizeof(BaseE) << "\n"
              << "vptr D and E " << vpt1 << "\n"
              << "vptr A and C " << vpt2 << "\n"
              << "vptr B " << vpt3 << "\n";
return 0;
}

The output from this code run is the following:

called here: 140736308965696
called here: 140736308965696
called here: 140736308965696
e is at: 140736308965696
d is at: 140736308965696
a is at: 140736308965704
c is at: 140736308965704
b is at: 140736308965712
total size is 24
vptr D and E 4390608
vptr A and C 4390648
vptr B 4390680

This suggest the following memory layout for BaseE obj (with the pointer size being 8 bytes).


8 bytes, BaseD subobject, vptr to D table only

8 bytes, BaseA subobject, vptr to A table only

8 bytes, BaseB subobject, vptr to B table only.

Here ePtr and dPtr both point to BaseD subobject, both aPtr and cPtr point to BaseA subobject and bPtr to BaseB subobject.

My question is now is what thunk code will the compiler generate to adjust the this pointer in two calls above to b() through pointers cPtr and bPtr to make sure this points correctly to ePtr when BaseE implementation of b() is called? Since cPtr and bPtr have different address does the thunk need to be aware to adjust differently based on what type of Base class pointer is passed to it?

  • 1
    Implementation defined. But first observation is different thunks per vtables. Why should it matter by the way? – Red.Wave Jun 02 '18 at 16:34
  • The thunking code is an implementation detail, not specified by the standard. You will need to disassemble your code to see what your compiler is doing. Most compilers (maybe all of them?) allow emitting assembly file, which will make it easier to see what is going on. – Eljay Jun 02 '18 at 17:18
  • Remark: `*reinterpret_cast(v)` where `v` is `&a` can be written `reinterpret_cast(a)` – curiousguy Jun 04 '18 at 19:26
  • @Red.Wave "_Implementation defined_" There is nothing in the std that says it is. – curiousguy Jun 04 '18 at 19:28
  • @curiousguy which part of std points out thunks? Implementation in this context refers to the impl of compiler/std. – Red.Wave Jun 05 '18 at 05:54
  • @Red.Wave There is nothing in the std about thunks. – curiousguy Jun 05 '18 at 06:14
  • @curiousguy whatever the standard does not specify is called implementation defined. i.e. the implementors of that feature can interpret and treat it at will. Existence of vtable is not required by the std, nor are address thunks. The invariants are std casting rules. – Red.Wave Jun 05 '18 at 06:18
  • @Red.Wave "implementation dependent"? – curiousguy Jun 24 '18 at 19:58

0 Answers0