Virtual class inheritance object size issue
Asked Answered
V

2

3

Here, in this code, the size of ob1 is 16 which is fine(because of the virtual pointer) but I can't understand why the size of ob2 is 24.

#include <iostream>
using namespace std;
class A {
    int x;
};
class B {
    int y, z;
};
class C : virtual public A {
    int a;
};
class D : virtual public B {
    int b;
};
int main() {
    C ob1;
    D ob2;
    cout << sizeof(ob1) << sizeof(ob2) << "\n";
}

I expect the size of ob2 as 20, but the output is 24

Vaientina answered 13/8, 2019 at 15:44 Comment(17)
The virtual mechanism requires some overhead. Some compilers place that overhead into the structure.Zoon
You should make a habit of always terminating your output by either '\n' or std::endl.Zoon
There's usually a vtable pointer introduced with virtual inheritance. That might explain the difference in size,Philhellene
@πάνταῥεῖ: "There's usually a vtable pointer ". Is virtual inheritance introducing a vtable pointer? I don't believe!Workman
I expect the size of ob2 as 20, but the output is 24 What's your exact platform? Likely something in the internals of the object has an 8-byte alignment requirement. Or the platform itself imposes an 8-byte alignment requirement in general.Hypothesize
@Workman Yes. virtual inheritance require some runtime mechanism usually implemented using a vtable. The vtable may contain a pointer to the base class instance for example.Izettaizhevsk
@AndrewHenle I was also thinking the same but I can't identify the exact reason for the 8-byte alignment. I am using Ubuntu 64-bitVaientina
@GuillaumeRacicot: I found also an article here: en.wikipedia.org/wiki/Virtual_inheritance which tells "However this offset can in the general case only be known at runtime, ...". I didn't catch the point, which information is needed and only known in runtime.Workman
@πάνταῥεῖ Yes. That's why I got 16 for ob1 as size of vtable pointer is 8. But with the same logic it should be 20 for ob2Vaientina
@Workman when using virtual inheritance, the offset of the base class is only known at runtime, since it may change depending on the type of a class furter down in the hierarchy.Izettaizhevsk
@GuillaumeRacicot "Usually" is too strong here; there are widely different implementation details re: VI unlike virtual functions and single inheritance which is extremely uniform (though not to the point of binary compatibility) among compilers. MSVC++ and GCC have very different approaches. Also, by definition vtables never contain ptr to user class instances.Burgomaster
For such Q it is generally a good idea to begging with printing the size of various relevant scalar types so we have a good idea about the size of basic types on your platform and C++ implementation, which can vary a lot between popular implementations. These can be guessed from the size of compound types but it's easier to not have to.Burgomaster
@ThomasMatthews "Some compilers place that overhead into the structure." Just some? Which ones don't?Burgomaster
@curiousguy: There are a lot of compilers out there and I don't know the behavior of all of them. According to the standard, there is no requirement for a compiler to use vtables. BTW, there is the embedded systems world and it's compilers may perform differently than Desktop compilers (because many embedded systems are resource constrained).Zoon
@ThomasMatthews Indeed, but using a vptr to implement virtual stuff is inherently a very efficient way to retrieve information, it's local, it's bounded time and bounded space, and doesn't require global mutable state. Alternatives can be made up, but they mostly require a mutable global state, that means locks all over the place if you want to support MT, also non constant time for many operations. And it's inefficient for creating short lived polymorphic objects (even if runtime polymorphism isn't used). Using vtables isn't just a convenience and ABI compat issue, it's an efficiency issue.Burgomaster
@curiousguy, To answer your question, "Which ones don't?", could you please perform some metrics on all the compilers? Here are some to name a few: Keil, IAR, Visual Studio, G++, CLang, MinGW, Intel, ARM, Greenhills, and Metaware to name a few.Zoon
@ThomasMatthews How many of these compilers don't claim to follow a well known ABI? All well known C++ ABI are vtable based. (There are of course wide differences in the fine details of the vtable representation.) Unless a compiler isn't designed to be binary compatible with others, it has to use vtables.Burgomaster
A
8

One possible layout for objects of type D is:

+----------+
| y        |   The B subobject (8 bytes)
| z        |
+----------+
| vptr     |   vtable pointer (8 bytes)
|          |
+----------+
| b        |   4 bytes
+----------+
| unused   |   4 bytes (padding for alignment purposes)
+----------+

That would make sizeof(ob2) 24.

Alignment requirements are defined by an implementation. Most of the time, the size of the largest member object or subobject dictates the alignment requirement of an object. In your case, the size of the largest object, the vtable pointer, is 8 bytes. Hence, the implementation aligns objects at 8 bit boundaries, adding padding when necessary.

Alexandro answered 13/8, 2019 at 16:26 Comment(4)
Obviously the correct answer. Perhaps you can elaborate on the for alignment purposes bit?Dioptase
I am interested in the question: Why we need a runtime vtable pointer in that case. I can't catch the point why it is dependent on runtime here as the inheritance hierarchy is fully known at compile time. Thanks... maybe another question :-)Workman
@Klaus, yes, that would be another question.Alexandro
@Workman Inheritance relations is not more known at compile time than function members are. Virtual stuff can be overridden in derived classes unlike non virtual stuff.Burgomaster
D
2

To implement the virtual inheritance, D contains as data member a pointer, which on a 64 bit system requires 8 bytes. Moreover, these 8 bytes must be aligned to a 8-byte memory boundary. This latter requirement in turn mandates that D itself be aligned to a 8-byte memory boundary. The simplest way to implement that is to make sizeof(D) a multiple of 8 by padding it with unused bytes (21-24).

Dioptase answered 13/8, 2019 at 16:56 Comment(1)
"padding it with unused bytes" and that (almost certainly correct) hypothesis can be verified by adding a dummy data member at the end, the size of hypothesized padding, and checking that the total size doesn't change.Burgomaster

© 2022 - 2024 — McMap. All rights reserved.