Difference in class size during Virtual inheritance



  • I am using 32 bit GCC compiler.

    Case 1:

    class Base
    {
    };
    
    class Derived : virtual public Base
    {
    };
    

    size of Derived class is 4.

    Case 2:

    class Base
    {
    virtual void hello();
    };
    
    class Derived : virtual public Base
    {
    };
    

    size of Derived class is still 4.

    Case 3:

    class Base
    {
    int a;
    virtual void hello();
    };
    
    class Derived : virtual public Base
    {
    };
    

    size of Derived class is 12.

    Can anyone please help understand the memory layout concept here.

    In case 2, can some one please explain me why sizeof(Derived) is 4.

    ( my half-baked understanding is that base class has 4 bytes(virtual function) and derived class inherits that 4 bytes + size for virtual base pointer, hence the total should be 8.)


  • Moderators

    @vinoth-rajendran4 said in Difference in class size during Virtual inheritance:

    Can anyone please help understand the memory layout concept here.

    In case 2, can some one please explain me why sizeof(Derived) is 4.

    ( my half-baked understanding is that base class has 4 bytes(virtual function) and derived class inherits that 4 bytes + size for virtual base pointer, hence the total should be 8.)

    I don't know the exact answer (perhaps @kshegunov does?), but I do believe the size of a class's vtable is separate from the size of a class's instance.

    I will also point out that the memory layout is implementation-specific. You'll find that with MSVC 2017 32-bit, sizeof(Derived) in case 2 is 8, but it drops to 4 if you change to non-virtual inheritance. With GCC, the size is 4 with both virtual and non-virtual inheritance.


  • Qt Champions 2017

    I have been summoned.

    @jksh said in Difference in class size during Virtual inheritance:

    I don't know the exact answer (perhaps @kshegunov does?), but I do believe the size of a class's vtable is separate from the size of a class's instance.

    Correct, the vtable is part of the .text section, so its size has no bearing on the objects. There's one single vptr that's added to the object in the beginning of the object's data block which points to the class' vtable. When a polymorphic call is required the vptr is used to do the indirection at runtime. The vptr is initialized in the class' constructor after the parent's constructor's run, but before the constructor's initializer list. The implication is that from the point of view of Base::Base() the object is of type Base no matter where the constructor call originated, meaning that if you override a method in Derived, but call it in Base::Base() then Base's implementation is going to be called, that is - you can't have polymorphic calls proper in constructors. Thus if Base's virtual method is pure virtual and you call it in the constructor, you're going to segfault.

    Theory aside. In the above example:
    Case 1 and 2:
    sizeof(Derived) == sizeof(void *) which is the size of the vptr.
    Case 3:
    sizeof(Derived) == sizeof(void *) + sizeof(int) - you have one vptr and one integer member derived from Base. I assume you're running a 64bit show, so sizeof(void *) == 8 in this case. The int is 4 bytes, so you get a total of 12.

    Edit: Missed that you're compiling for 32 bits.
    In that case sizeof(void *) == 4, however padding applies to data members and alignment applies to data blocks. Depending on how the compiler does it (I think by default it pads to the hardware word size) it may differ, but in this case I'm pretty sure the additional 4 bytes you see is the member padding.


  • Moderators

    I think it's a bit more complicated than just padding. If you use "-fdump-class-hierarchy" switch you can see some info about how the classes are structured. If you look at the vtable offsets you can see GCC has some clever trickery that allow it to use the same vtable offset for both base and derived classes in some cases, and thus shrink the size of the class by one pointer. An interesting thing is that for case 2 you will get size of derived class 4 whether you use virtual or normal inheritance, but for case 3 it will vary depending on the type of inheritance and be either 12 (for virtual) or 8 (for normal).

    I think it does some sort of vtable merge so that the class object only holds one pointer, but I won't try to pretend to know how it works exactly. There's probably some beefy document somewhere out there describing the trickery in excruciating detail, but the point is that it's a vendor specific compiler optimization of some sort (MSVC doesn't seem to behave like that).


 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.