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.)
-
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.)
@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 is8
, but it drops to4
if you change to non-virtual inheritance. With GCC, the size is4
with both virtual and non-virtual inheritance. -
@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 is8
, but it drops to4
if you change to non-virtual inheritance. With GCC, the size is4
with both virtual and non-virtual inheritance.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 singlevptr
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 thevptr
is used to do the indirection at runtime. Thevptr
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 ofBase::Base()
the object is of typeBase
no matter where the constructor call originated, meaning that if you override a method inDerived
, but call it inBase::Base()
thenBase
's implementation is going to be called, that is - you can't have polymorphic calls proper in constructors. Thus ifBase
'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 thevptr
.
Case 3:
sizeof(Derived) == sizeof(void *) + sizeof(int)
- you have onevptr
and one integer member derived fromBase
.I assume you're running a 64bit show, soThesizeof(void *) == 8
in this case.int
is 4 bytes, so you get a total of 12.Edit: Missed that you're compiling for 32 bits.
In that casesizeof(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. -
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).
-
@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 is8
, but it drops to4
if you change to non-virtual inheritance. With GCC, the size is4
with both virtual and non-virtual inheritance.@JKSH said in Difference in class size during Virtual inheritance:
I will also point out that the memory layout is implementation-specific.
This is the main thing to remember in this discussion. The language specification makes no rules about implementation, just behaviour. being concerned about vtables is usually indicative of a system design error.
-
@JKSH said in Difference in class size during Virtual inheritance:
I will also point out that the memory layout is implementation-specific.
This is the main thing to remember in this discussion. The language specification makes no rules about implementation, just behaviour. being concerned about vtables is usually indicative of a system design error.
@Kent-Dorfman said in Difference in class size during Virtual inheritance:
being concerned about vtables is usually indicative of a system design error.
Or working on a low level optimization. In my line of work for example this is nothing unusual. Vtables do have measurable impact on performance and memory consumption. It's just that there are cases where it doesn't matter that much and then there are cases where it does, a lot.
Relying on class layout, sizes and alignment is futile in generic code as you never know what they are in all possible configs, but sometimes you get to work on a specific locked hardware and toolchain combination (e.g. game consoles or medical equipment) and in those cases it's very much desirable to get the most out of that setup, down to the layout and vtable specific bytes.
-
@Kent-Dorfman said in Difference in class size during Virtual inheritance:
being concerned about vtables is usually indicative of a system design error.
Or working on a low level optimization. In my line of work for example this is nothing unusual. Vtables do have measurable impact on performance and memory consumption. It's just that there are cases where it doesn't matter that much and then there are cases where it does, a lot.
Relying on class layout, sizes and alignment is futile in generic code as you never know what they are in all possible configs, but sometimes you get to work on a specific locked hardware and toolchain combination (e.g. game consoles or medical equipment) and in those cases it's very much desirable to get the most out of that setup, down to the layout and vtable specific bytes.
@Chris-Kawa said in Difference in class size during Virtual inheritance:
Vtables do have measurable impact on performance and memory consumption.
I think they (HW guys) introduced a vtable prefetcher specifically to mitigate that, didn't they?
-
@Chris-Kawa said in Difference in class size during Virtual inheritance:
Vtables do have measurable impact on performance and memory consumption.
I think they (HW guys) introduced a vtable prefetcher specifically to mitigate that, didn't they?
@kshegunov said in Difference in class size during Virtual inheritance:
I think they (HW guys) introduced a vtable prefetcher specifically to mitigate that, didn't they?
It's like with branch predictors, caching and such. They mitigate problems but don't eliminate them. In some cases you still need to help them to help you.
-
@kshegunov said in Difference in class size during Virtual inheritance:
I think they (HW guys) introduced a vtable prefetcher specifically to mitigate that, didn't they?
It's like with branch predictors, caching and such. They mitigate problems but don't eliminate them. In some cases you still need to help them to help you.
@Chris-Kawa said in Difference in class size during Virtual inheritance:
It's like with branch predictors, caching and such. They mitigate problems but don't eliminate them. In some cases you still need to help them to help you.
No argument there. I was just left with the impression that in modern machines there was a dedicated polymorphic prefetcher (with a branch predictor) specifically to make most of the weight of the virtual indirection go away. I was just wondering if you knew that to be a fact, or I'm just imagining things.
-
@kshegunov I'm not an expert on that but I do see boost when optimizing for reduced vtable usage, so either my hardware doesn't do that good of a job or something else is going on. Thanks for a nudge, I guess it's something worth looking into.