I encounter this problem when tackling with virtual inheritance. I remember that in a non-virtual inheritance hierarchy, object of sub-class hold an object of its direct super-class. What about virtual inheritance? In this situation, does object of sub-class hold an object of its super-class directly or just hold a pointer pointing to an object of its super-class?
By the way, why the output of the following code is:
sizeof(A): 8
sizeof(B): 20
sizeof(C): 20
sizeof(D): 36
Code:
#include <iostream>
using namespace std;
class A{
char k[ 3 ];
public:
virtual void a(){};
};
class B : public virtual A{
char j[ 3 ];
public:
virtual void b(){};
};
class C : public virtual A{
char i[ 3 ];
public:
virtual void c(){};
};
class D : public B, public C{
char h[ 3 ];
public:
virtual void d(){};
};
int main( int argc, char *argv[] ){
cout << "sizeof(A): " << sizeof( A ) << endl;
cout << "sizeof(B): " << sizeof( B ) << endl;
cout << "sizeof(C): " << sizeof( C ) << endl;
cout << "sizeof(D): " << sizeof( D ) << endl;
return 0;
}
Thanks in advance.
Kind regards.
The virtual base object is somewhere in the memory block that belongs to the object (the memory with size = sizeof(object)). Because several sub objects of different types can be combined in various ways but must share the same base object, a offset pointer is needed for each sub object to find out the virtual base object. Without virtual inheritance, the offset to find out the corresponding base object is fixed at compile time for each class type.
The sizeof values depend on your compiler and machine, but the following assumptions are very common:
assumption: pointer size is 4 bytes
assumption: class size is rounded up to multiple of 4 bytes
sizeof(A): 8 -> 1 pointer to vtable (virtual method)
+ 3 chars -> 4+3=7
-> round up to 8
sizeof(B): 20 -> 8 + 1 pointer to vtable (virtual method)
+ 1 offset pointer to virtual base
+ 3 chars -> 8 + 4 + 4 + 3 = 19
-> round up to 20
sizeof(C): 32 -> 20 + 1 pointer to vtable (virtual method)
+ 1 offset pointer to virtual base
+ 3 chars
-> 20 + 4 + 4 + 3 = 31 // this calculation refers to an older
-> round up to 32 // version of the question's example
// where C had B as base class
The calculations are guessed because the real calculation must exactly know how the compiler works.
Regards,
Oliver
More details why an extra offset pointer is needed:
Example:
class B : virtual public A {...};
class C : virtual public A {...};
class D1 : public B {...};
class D2 : public B, C {...};
possible memory layout for D1:
A
B
D1
possible memory layout for D2:
A
C
B
D2
in the second case sub object B needs another offset to find its base A
An object of type D2 consists of a memory block, where all the parent object parts are contained, i.e. the memory block for an object of type D2 has a section for the A member variables, the C member variables, the B member variables and the D2 member variables. The order of these sections is compiler dependent, but the example shows, that for multiple virtual inheritance a offset pointer is needed, that points within the object's total memory block to the virtual base object. This is needed because the methods of class B know only one this pointer to B and must somehow calculate where the A memory part is relative to the this pointer.
Calculation sizeof(D):
sizeof(D): 36 -> A:3 chars + A:vtable
+ B:3 chars + B:vtable + B:virtual base pointer
+ C:3 chars + C:vtable + C:virtual base pointer
+ D:3 chars + D:vtable
= 3 + 4
+ 3 + 4 + 4
+ 3 + 4 + 4
+ 3 + 4
= 36
The above calculation is probably wrong ;-) ...
I'm not sure whether the D part has its own vtable pointer or not (this is all highly compiler dependent).
I now think that it could be that the D part use the vtable pointer entry of its parent classes and that the 4 extra bytes are used for alignment each part (multiple of 8 bytes):
So this calculation is probably more correct:
sizeof(D): 36 -> A:3 chars + A:vtable + A:alignment
+ B:3 chars + B:vtable + B:virtual base pointer + B:alignment
+ C:3 chars + C:vtable + C:virtual base pointer + C:alignment
+ D:3 chars + D:alignment
= 3 + 4 + 1
+ 3 + 4 + 4 + 1
+ 3 + 4 + 4 + 1
+ 3 + 1
= 36
I see three point analysis for the above question
a. Virtual Inheritance
"Virtual inheritance is a mechanism whereby a class specifies that it is willing to share the state of its virtual base class. Under virtual inheritance, only one, shared base-class subobject is inherited for a given virtual base regardless of how many times the class occurs as a virtual base within the derivation hierarchy. The shared base-class subobject is called a virtual base class." ... From Lippman
Virtual inheritance only avoids duplicate sub-objects inherited from multiple inheritance. But this does not indicate in any way that the base class objects will not be sub-objects. On the contrary, the sub-object (atleast one copy would be present - I mean would be included in sizeof() operation) even during the virtual inheritance.
b. virtual function
Virtual function is for dynamic binding of member functions of objects involved in hierarchy. So even this does not have any significance towards sub-object arrangements.
c. Implementation of the sub-objects
This is totally compiler dependent, and for all reasons would be very difficult to determine - in its implementation. However, we can confirm that the sizeof() of the object would include the size of the base class (sub) objects also - and we can visualize them as having the base class object embedded in them.
Each object of the inherited function will definitely contain space for the sub-objects.
HTH
does object of sub-class hold an object of its super-class directly
Yes, that is how it works whether the inheritance is virtual or not. I would use the word "contain" vs. "hold" however.
If your hierarchy looked like this, with no virtual inheritances anywhere:
# W <--- base class
# / \
# X Y <--- subclasses of W
# \ /
# Z <--- most derived class
Then Z will have two copies of W. But if you make the X-->W and Y-->W inheritances virtual, then Z will only have one copy of W because Z's two superclasses share their common base class.
# W
# / \ <--- make these two virtual to eliminate duplicate W in Z.
# X Y
# \ /
# Z
In your example:
class A{...};
class B : public virtual A{...};
class C : public virtual B{...}; // Edit: OP's code had this typo when I answered
class D : public B, public C{...};
Having B inherit virtually from A isn't necessary. The only virtual inheritances you need are C-->B and D-->B, since that is where the diamond "merges" going up the inheritance hierarchy:
# What you have | What you want?
# A | A
# / | /
# /v | /
# / | /
# B | B
# / \ | / \
# /v \ | /v \v
# / \ | / \
# C ) | C )
# \ / | \ /
# \ / | \ /
# \ / | \ /
# D | D
Of course if you have other classes not shown that inherit from A as well as B, that changes things -- maybe the B-->A inheritance does need to be virtual if there is another diamond you didn't tell us about.
I remember that in a non-virtual inheritance hierarchy, object of sub-class hold an object of its direct super-class.
That is not correct. Several implementations are going to do it this way, but it's not defined that way by Standard C++. Standard C++ does not specify how any of these things are to be implemented.
Virtual Inheritance is used only for some cases of multiple inheritance where a derived class multiply inherits from two base classes which themselves inherit from a common base class. An example of this is the iostream library, where istream and ostream inherit from basic_ios, and iostream inherits from istream and ostream (and so one iostream would have two basic_ios without virtual inheritance).
Unless you are in this specific scenario, you should not use virtual inheritance.
What about virtual inheritance? In this situation, does object of sub-class hold an object of its super-class directly or just hold a pointer pointing to an object of its super-class?
That is implementation defined. You do not need to know nor should you ever make any assumptions about this. Suffice to say that there is a runtime penalty for virtual inheritance, which is why you should avoid it when it is not needed.
Compare:
struct A {
void *vptr; // offset 0 size 4 alignment 4
char k[3]; // offset 4 size 3 alignment 1
char unnamed_padding; // offset 7 size 1
// total size 8 alignment 4
};
// MS:
struct base_B {
void *vptr; // offset 0 size 4 alignment 4
char j[3]; // offset 4 size 3 alignment 1
char unnamed_padding; // offset 7 size 1
A &a_subobject; // offset 8 size 4 alignment 4
// total size 12 alignment 4
base_B (&a_subobject) :a_subobject(a_subobject) {}
};
struct B {
base_B b; // offset 0 size 12 alignment 4
A a_subobject; // offset 12 size 8 alignment 4
// total size 20 alignment 4
B () : b(a_subobject) {}
};
struct base_C {
void *vptr; // offset 0 size 4 alignment 4
char i[3]; // offset 4 size 3 alignment 1
char unnamed_padding; // offset 7 size 1
A &a_subobject; // offset 8 size 4 alignment 4
// total size 12 alignment 4
base_C (&a_subobject) : a_subobject(a_subobject) {}
};
struct C {
base_C c;
A a_subobject; // offset 12 size 8 alignment 4
// total size 20 alignment 4
C () : c(a_subobject) {}
};
struct D {
// no new vptr!
// base_B is used as primary base: b_subobject.vptr is used as vptr
base_B b_subobject; // offset 0 size 12 alignment 4
base_C c_subobject; // offset 12 size 12 alignment 4
char h[3]; // offset 24 size 3 alignment 1
char unnamed_padding; // offset 27 size 1
A a_subobject; // offset 28 size 8 alignment 4
// total size 36 alignment 4
D (): b_subobject(a_subobject), c_subobject(a_subobject) {}
};
// GCC:
struct base_B {
void *vptr; // offset 0 size 4 alignment 4
char j[3]; // offset 4 size 3 alignment 1
char unnamed_padding; // offset 7 size 1
// total size 8 alignment 4
};
struct B {
base_B b; // offset 0 size 12 alignment 4
A a_subobject; // offset 8 size 8 alignment 4
// total size 16 alignment 4
};
struct base_C {
void *vptr; // offset 0 size 4 alignment 4
char i[3]; // offset 4 size 3 alignment 1
char unnamed_padding; // offset 7 size 1
// total size 8 alignment 4
};
struct C {
base_C b; // offset 0 size 12 alignment 4
A a_subobject; // offset 8 size 8 alignment 4
// total size 16 alignment 4
};
struct D {
// no new vptr!
// base_B is used as primary base: b_subobject.vptr is used as vptr
base_B b_subobject; // offset 0 size 8 alignment 4
base_C c_subobject; // offset 8 size 8 alignment 4
char h[3]; // offset 16 size 3 alignment 1
char unnamed_padding; // offset 19 size 1
A a_subobject; // offset 20 size 8 alignment 4
// total size 24 alignment 4
};
Related
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
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.
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).
I think sizeof(Base) should be 12. Why is it 16?
Without the virtual function, I get 4 and 8.
class Base{
public:
int i;
virtual void Print(){cout<<"Base Print";}
};
class Derived:public Base{
public:
int n;
virtual void Print(){cout<<"Derived Print";}
};
int main(){
Derived d;
cout<<sizeof(Base)<<","<<sizeof(d);
return 0;
}
expected result:12,16
actual result:16,16
why sizeof(Base) is not different of sizeof(Derived)
Because of the alignment introduced by the compiler.
That is architecture-dependent, but for sake of simplicity, I am going to assume we are referring a 64-bit architecture.
Scenario 64 bit / Clang 8.0.
The alignment of the type Base is 8 bytes:
alignOfBase(): # #alignOfBase()
mov eax, 8
ret
The layout of Base is composed by the variable member (int) and the virtual table (vtptr).
If we assume a "common" architecture where:
int is 4 bytes size.
vtptr is a pointer. On a 64 bit architecture is 8 bytes size.
We should have a sum of 4 + 8 = 12, as you expect.
However, we need to remember the alignment of Base is 8 bytes. Therefore consecutive Base types should be stored in location multiple of 8.
In order to guarantee that, the compiler introduces padding for Base. That's why Base is 16 bytes size.
For example, if we consider 2 consecutive Base (base0 and base1) without padding:
0: vtptr (base 0) + 8
8: int (base 0) + 4
12: vtptr (base 1) + 8 <--- Wrong! The address 12 is not multiple of 8.
20: int (base 1) + 4
With padding:
0: vtptr (base 0) + 8
8: int (base 0) + 4+4 (4 padding)
16: vtptr (base 1) +8 <--- Fine! The adress 16 is multiple of 8.
24: int (base 1) +4+4 (4 padding)
The same story is for Derived type.
The layout of Derived should be: vtptr + int + int, that is, 8 + 4 + 4 = 16.
The alignment of Derived is 8 too:
alignOfDerived(): # #alignOfDerived()
mov eax, 8
ret
Indeed, in this case, there is no need to introduce padding in order to keep the Derived aligned with the memory. The layout size will be the same as the real size.
0: vtptr (Derived 0)
8: int (Derived 0)
12: int (Derived 0)
16: vtptr (Derived 1) <---- Fine. 16 is multiple of 8.
24: int (Derived 1)
28: int (Derived 1)
This happens due to compiler deciding to align your classes.
If you want (or need) structs or classes to have their "real" sizes, you can use #pragma pack(1) like this:
#pragma pack(push, 1) // Set packing to 1 byte and push old packing value to stack
class Base{
public:
int i;
virtual void Print(){cout<<"Base Print";}
};
class Derived:public Base{
public:
int n;
virtual void Print(){cout<<"Derived Print";}
};
int main(){
Derived d;
cout<<sizeof(Base)<<","<<sizeof(d);
return 0;
}
#pragma pack(pop) // restore old packing value
As explained in one of the solutions of this question (Size of virtual pointer-C++) that you can calculate virtual pointer size in following manner :
struct virtual_base {
int data;
virtual_base() {}
virtual ~virtual_base() {}
};
struct non_virtual_base {
int data;
non_virtual_base() {}
~non_virtual_base() {}
};
int main() {
std::cout << sizeof( virtual_base ) - sizeof( non_virtual_base ) << '\n';
return 0;
}
But when i try this on cpp.sh http://cpp.sh/7o5av, without data (member variable) i get the size as 7 and with data size is coming out to be 12, so i failed to understand this behavior and any insights will be helpful and i know that size of empty class is 1 and in second with data member i expect this should come as 11 and not 7
You get 7 with no data member becuase the empty class has size 1 so while the virtual one contains a pointer to the virtual table of size 8: 8-1=7.
When data members are involved the result you get depends on the actual type of the members. If you use int then the difference is 12 becuase the vptr has to be aligned to multiple of 8.
This means that the int data member occupies bytes frm 0 to 4 and the vptr cannot be stored at byte 4 but starting from at byte 8. So the total size is 8+8=16 for the virtual struct. Try using a double and you will see that the difference is 8 as in the following code.
#include <iostream>
using namespace std;
struct virtual_base {
double data;
virtual_base() {}
virtual ~virtual_base() {}
};
struct non_virtual_base {
double data;
non_virtual_base() {}
~non_virtual_base() {}
};
int main() {
std::cout << sizeof( virtual_base ) - sizeof( non_virtual_base ) << '\n';
return 0;
}
try here: https://www.ideone.com/Ycpg64
virtual_base only contains the vftable pointer which is apparently 8 bytes on your platform. virtual_base also has an int, and the vftable is aligned to 8 bytes. So it's something like:
4 bytes for int | 4 padding bytes | 8 bytes for vftable pointer |
| x | x | x | x | | | | | v | v | v | v | v | v | v | v |
Please have a look at this. It might be helpful.
With int data member:
sizeof(virtual_base) is 16 [4(int)+ 4(padding) +8(vftable)] bytes
sizeof(non_virtual_base) is 4 bytes i.e. size of int.
Without int data member:
sizeof(virtual_base) is 8 bytes i.e the size of vftable pointer. There is no padding.
sizeof(non_virtual_base) without any data member is 1 byte.
When you declare any function as virtual in C++, the class receives a hidden member vptr, which points to vtable. This is used to choose which function should actually be called when dynamic polymorphism is used.
Disclaimer: I'll be using results from online compiler you posted, with the options you set (C++14, O2 optimization)
We can easily see that empty class has sizeof equal to 1, and sizeof class with virtual functions is 8.
Then, with data member, class without virtual functions gets sizeof 4 and the one with virtual functions get sizeof 16.
We can also check that sizeof pointer type (for example int*) is equal to 8
So, what happens here: empty class gets assigned size 1 by default. But, a class with virtual functions must have vptr member, which itself is 8 bytes long. This gives you 8-1 = 7
When you add member to non-virtual class, it simply gets the size of all it's members, in this case it's int, so you have size 4.
For virtual class, you already have vptr of size 8 and add int of size 4 to it. Here the mechanism of structure alignment kicks in. Presumably, the system it's compiling allows for access only to bytes which are on offset of multiply of 8, so the compiler, to optimize access time, adds artifial padding bytes, which hold no data. Basically, object of your class would look like this:
[int(4B)|padding(4B)|vptr(8B)]
This results in size of class being 16. So we get 16 - 4 = 12.
I'm using the example in C++14 §3.11/2:
struct B { long double d; };
struct D : virtual B { char c; }
After running the snippet below in clang, g++ and VS2015
#include <iostream>
struct B { long double d; };
struct D : /*virtual*/ B { char c; };
int main()
{
std::cout << "sizeof(long double) = " << sizeof(long double) << '\n';
std::cout << "alignof(long double) = " << alignof(long double) << '\n';
std::cout << "sizeof(B) = " << sizeof(B) << '\n';
std::cout << "alignof(B) = " << alignof(B) << '\n';
std::cout << "sizeof(D) = " << sizeof(D) << '\n';
std::cout << "alignof(D) = " << alignof(D) << '\n';
}
I got the following results:
clang g++ VS2015
sizeof(long double) 16 16 8
alignof(long double) 16 16 8
sizeof(B) 16 16 8
alignof(B) 16 16 8
sizeof(D) 32 32 16
alignof(D) 16 16 8
Now, after uncommenting the virtual in the definition of struct D in the code above and running the code again for clang, g++ and VS2015, I obtained the following results:
clang g++ VS2015
sizeof(long double) 16 16 8
alignof(long double) 16 16 8
sizeof(B) 16 16 8
alignof(B) 16 16 8
sizeof(D) 32 32 24
alignof(D) 16 16 8
I have no doubts about the results obtained above, with one single exception: why did the sizeof(D) increased from 16 to 24 in VS2015?
I know this is implementation defined, but there might be a reasonable explanation for this increase in size. This is what I'd like to know if possible.
If you actually make use of the virtual aspect of virtual inheritance, I think the need for the vtable pointer becomes clear. One item in the vtable is likely the offset of the start of B from the start of D.
Assume E inherits virtually from B and F inherits from both E and D such that the D inside an F ends up using the B inside the E for its base class. In a method of D that doesn't know it is a base class of F how could you find members of B without info stored in the vtable?
So clang and G++ changed 8 bytes of padding into a vtable pointer and you thought there was no change. But VS2015 never had that padding, so it needed to add 8 bytes for vtable pointer.
Maybe a compiler notices that the only use of the vtable pointer is in an inefficient scheme for computing the base pointer. So maybe that is optimized into simply having a base pointer instead of a vtable pointer. But that would not change the need for the 8 bytes.
When there is a virtual base object, the location of the base object relative to the address of a derived object is not statically predictable. Notably, if you extend your class hierarchy a bit it becomes clear that there can be multiple D subobjects which still need to reference just one B base object:
class I1: public D {};
class I2: public D {};
class Most: public I1, public I2 {};
You can get a D* from a Most object by either converting first to I1 or first to I2:
Most m;
D* d1 = static_cast<I1*>(&m);
D* d2 = static_cast<I2*>(&m);
You'll have d1 != d2, i.e., there are genuinely two D subobjects, but static_cast<B*>(d1) == static_cast<B*>(d2), i.e., there is just one B subobject. To determine how to adjust d1 and d2 to find a pointer to the B subobject a dynamic offset is needed. The information on how to determine this offset needs to be stored somewhere. The storage for this information is the likely source of the extra 8 bytes.
I don't think the object layout for types in MSVC++ is [publicly] documented, i.e., it is impossible to tell for sure what they are doing. From the looks of it they embed a 64 bit object to be able to tell where the base object lives relative to the address of the derived object (a pointer to some type information, a pointer to a base, an offset to the base, something like that). The other 8 byte most likely originate from the need to store a char plus some padding to have the object aligned at a suitable boundary. That seems similar to what the other two compilers do except that they used 16 bytes for long double to start with (probably it is just 10 bytes padded to a suitable alignment).
To understand how the C++ object model could work, you might want to have a look at Stan Lippman's "Inside the C++ Object Model". It is a bit dated but describes potential implementation techniques. Whether MSVC++ uses any of them I don't know but it gives ideas what might be used.
For the object model used by gcc and clang you can have a look at the Itanium ABI: they essentially use the Itanium ABI with the minor adjustments to the actually used CPU.
In visual studio, the default behaviour is that all structs are aligned along a 8 byte boundary. i.e.even if you do
struct A {
char c;
}
and then check sizeof(A), you will see that it is 8 bytes.
Now, in your case, when you changed the type of inheritance of struct D to be virtual, the compiler has to do something extra to accomplish this. First, it creates a virtual table for the struct D. What does this vtable contain? It contains a single pointer to the offset of struct B in memory.Next, it adds a vptr at the head of struct D which points to the newly created vtable.
Hence, now struct D should look like:
struct D : virtual B { void* vptr; char c; }
So, the size of D will be:
sizeof (long double) + sizeof (void*) + sizeof (char) = 8 + 8 + 1 = 17
This is where the boundary alignment we discussed in the beginning comes in. Since all structs have to be aligned to an 8 byte boundary and struct D is only 17 bytes, the compiler adds 7 padding bytes to the struct to make it aligned to a 8 byte boundary.
So the size now becomes:
Size of D = Size of elements of D + Padding bytes for byte alignment = 17 + 7 = 24 bytes.
This question already has an answer here:
Size of class with virtual function
(1 answer)
Closed 8 years ago.
Have a look at these 2 cases:
class A {
public:
int a;
A () { a = 10;}
void foo () {std::cout << "a = " << a << std::endl;}
};
Here sizeof(A) gives 4 bytes, which makes sense.
class A {
public:
int a;
A () { a = 10;}
virtual void foo () {std::cout << "a = " << a << std::endl;}
};
Here sizeof(A) gives 16 bytes as opposed to 12 bytes (4 + 8 for pointer).
Is there any explanation in terms of memory alignment for this ?
It's up to the compiler how virtual functions are implemented, but what's likely happening here is it wants/needs to align the 8-byte pointer to the virtual dispatch table on a multiple-of-8 memory address. Then there's either { 4 bytes a, 4 padding, 8 vdt pointer } or { 8 vdt pointer, 4 bytes a, 4 padding } - the latter's less obvious, but consider that arrays of A need to be contiguous and spaced per sizeof(A), so 12's rounded up to 16 given the 8-byte alignment.
FIRST in virtual dispatch table, visual functions need a pointer size , in 32 bits computer, the size of pointer is 4 bytes, int 64 bits it's 8 bytes. so I think your computer is 64 bits.
SECOND the sizeof(A) should consider the padding and memory alignment .
so 16 bytes is arranged like that: 4 bytes(int a) + 8 bytes(a virtual function pointer int 64 bits) + 4 bytes padding(because the max size of elements in A is the virtual function pointer which is 8 bytes, so the sizeof(A)should be the integral multiples of 8 bytes )