Question regarding Virtual

Hello, I am currently learning about how virtual is implemented in the Itanium ABI. Here is the layout of a class Bat that derives from the classes Bird and Mammal (in that order):

https://alschwalm.com/blog/static/content/images/2017/01/Bat-Layout--1-.png

As you can see, the vptr and data for the Mammal is somewhere in the middle of the Bat object. My question is, in cases of multiple inheritance, how is it determined which subobject of the class Bat (Bird or Mammal) will appear at the beginning of the object and which will appear in the middle? Is it determined by the order it is listed when inheriting from them?


Last edited on
> in cases of multiple inheritance, how is it determined which subobject of the class
> will appear at the beginning of the object and which will appear in the middle?

When multiple inheritance is involved, the class is not of a StandardLayoutType;
the standard has nothing to say about its lay out.


> Is it determined by the order it is listed when inheriting from them?

In most implementations, other things being equal, yes.

Quite often, other things may not be equal. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>

// caveat: this macro is not portable
#define OFFSET_OF_BASE( derived, base ) \
( (std::ptrdiff_t)(const base*)(const derived*)1'000'000 - 1'000'000 )

namespace one
{
    struct A { char c[121]; };
    struct B { int i ; };
    struct C : A, B {} ;
}

namespace two
{
    struct A { char c[121]; };
    struct B { int i ; virtual ~B() = default ; }; // B has a virtual function
    struct C : A, B {} ;
}

int main()
{
    {
        using namespace one ; // sub-object of type A is at an offset of zero
        std::cout << OFFSET_OF_BASE(C,A) << ' ' << OFFSET_OF_BASE(C,B) << '\n' ; // 0, 124
    }

    {
        using namespace two ; // sub-object of type B is at an offset of zero
        std::cout << OFFSET_OF_BASE(C,A) << ' ' << OFFSET_OF_BASE(C,B) << '\n' ; // 12, 0
    }
}

http://coliru.stacked-crooked.com/a/5e6289b39a308f89
https://rextester.com/LTSE25693
I'm not exactly following how your OFFSET_OF_BASE macro works exactly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>

// caveat: this macro is not portable
#define OFFSET_OF_BASE( derived, base ) \
( (std::ptrdiff_t)(const base*)(const derived*)1'000'000 - 1'000'000 )

struct A { char c[121]; };
struct B { int i ; };
struct C : A, B {} ;

int main()
{
        const C c{} ; // consider an object of type C

        const C* pc = std::addressof(c) ; // take its address
        const B* pb = static_cast<const B*>(pc) ; // get the address of the base class sub-obect
        // note that the cast is superfluous here; it is there to mimic the cast in the macro

        // get the numeric value of these two pointers
        std::intptr_t pc_value = reinterpret_cast<std::intptr_t>(pc) ;
        std::intptr_t pb_value = reinterpret_cast<std::intptr_t>(pb) ;

        // subtract one numeric value from the other to get the offset in bytes
        std::cout << "offset of sub-oject B == " << pb_value - pc_value << '\n'

                  // same as above, using old-style casts
                  << "offset of sub-oject B == "
                  << (std::intptr_t)(const B*)(const C*)pc_value - pc_value << '\n'

                  // the macro works with the supposition of a fictitious object of type derived
                  // which is at an address which has a numeric value of 1'000'000,
                  // and for brevity, uses old-style casts
                  << "  OFFSET_OF_BASE(C,B) == " << OFFSET_OF_BASE(C,B) << '\n' ;
}

http://coliru.stacked-crooked.com/a/966403d0dce4136d
I'm coming back to this thread because I had a question regarding your first post. In your first usage of OFFSET_OF_BASE (using namespace one), the sub-object is B and is located at an offset of 124 (which is expected, 120 bytes for the char array and 4 bytes for the int = 124), but the subobject for namespace two is A, located 12 bytes after B (at the beginning of the object).
If I am correct (and please correct me if I'm wrong), all the B object holds is a single integer (usually 4 bytes) and a vptr (8 bytes), thus it equals 12. Does that sound right? In cases of a polymorphic base class and a non-polymorphic class in multiple inheritance, will the polymorphic one always appear first in the derived object, despite the order?
Last edited on
> all the B object holds is a single integer (usually 4 bytes) and a vptr (8 bytes),
> thus it equals 12. Does that sound right?

Yes. It would have been larger if the pointer had a more stringent alignment requirement.
http://coliru.stacked-crooked.com/a/4f69e5df2fd300c6


> In cases of a polymorphic base class and a non-polymorphic class in multiple inheritance,
> will the polymorphic one always appear first in the derived object, despite the order?

Nothing is guaranteed by the standard.
However most implementations would favour placing the object of the polymorphic base class at an offset of zero, since it would be a more efficient layout.
Thank you.
So in the example code you gave, the object is 16 bytes, with 4 bytes being extra padding because of the alignment, right?
Right.
How come when I do this without explicitly setting the alignment to 8 bytes, it still returns 16?

https://rextester.com/YFZEK72748
On this implementation, the default alignment of const void* has the same value (8)
https://rextester.com/RRBJEF52416
Registered users can post here. Sign in or register to post.