Why did sizeof (D) increase by 8 bytes in VS2015 when I received D from the virtual database?

I use an 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 virtual in the definition of struct D in the above code and re-running the code for clang, g ++ and VS2015, 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 24 alignof(D) 16 16 8 

I have no doubt about the results obtained above, with one single exception: why did the value of sizeof(D) increase from 16 to 24 in VS2015?

I know this is an implementation, but there may be a reasonable explanation for this increase in size. This is what I would like to know if possible.

+6
source share
3 answers

If you really use the virtual aspect of virtual inheritance, I think the need for a vtable pointer becomes clear. One item in the vtable is probably the offset of the start of B from the start of D

Suppose that E inherits in fact from B and F inherits from both E and D , so that D inside a F ends with B inside E for its base class. In method D , which does not know that it is the base class of F , how could you find members of B without the information stored in the vtable?

So, clang and g ++ changed 8 padding bytes to a vtable pointer, and you thought there were no changes. But VS2015 never had this add-on, so it needed to add 8 bytes for the vtable pointer.

Maybe the compiler notices that the only use of the vtable pointer is an inefficient base pointer computation scheme. Therefore, perhaps this is optimized for a simple base pointer instead of a vtable pointer. But that would not change the need for 8 bytes.

+1
source

When there is a virtual base object, the location of the base object relative to the address of the derived object is not statically predictable. It is noteworthy that if you expand the class hierarchy a bit, it becomes clear that there may be several subobjects D that still need to reference only one base object B :

 class I1: public D {}; class I2: public D {}; class Most: public I1, public I2 {}; 

You can get D* from the Most object by converting it first to I1 or first to I2 :

 Most m; D* d1 = static_cast<I1*>(&m); D* d2 = static_cast<I2*>(&m); 

You will have d1 != d2 , i.e. there really are two subobjects D , but static_cast<B*>(d1) == static_cast<B*>(d2) , i.e. there is only one subobject B To determine how to adjust d1 and d2 to find a pointer to subobject B , dynamic offset is required. Information on how to determine this offset must be stored somewhere. Storage for this information is a likely source of an additional 8 bytes.

I do not think that the layout of the object for types in MSVC ++ is [publicly] documented, i.e. it’s impossible to say exactly what they are doing. In appearance, they implement a 64-bit object in order to be able to indicate where the base object is relative to the address of the derived object (a pointer to some type information, a pointer to the base, the base offset, something like this). The other 8 bytes, most likely, come from the need to store char plus some addition so that the object is aligned on a suitable border. This is similar to what the other two compilers do, except that they used 16 bytes for a long double to start with (perhaps just 10 bytes, complemented by suitable alignment).

To understand how the C ++ object model can work, you can take a look at Stan Lippman's "Inside the C ++ Object Model" . It is a bit outdated, but describes possible implementation methods. Is MSVC ++ any of these, I don’t know, but it gives ideas that can be used.

For the object model used by gcc and clang, you can take a look at Itanium ABI : they essentially use Itanium ABI with little tweaks to the actual CPU used.

0
source

In visual studio, the default behavior is that all structures are aligned along a border of 8 bytes. that is, if you do

 struct A { char c; } 

and then mark sizeof(A) , you will see that it is 8 bytes.

Now, in your case, when you change the inheritance type of structure D to be virtual, the compiler must do something extra to accomplish this. First, it creates a virtual table for structure D. What does this vtable contain? It contains a single pointer to the offset of structure B in memory. He then adds vptr at the head of the D structure, which points to the newly created vtable.

Therefore, now the structure D should look like this:

  struct D : virtual B { void* vptr; char c; } 

So size D will be:

 sizeof (long double) + sizeof (void*) + sizeof (char) = 8 + 8 + 1 = 17 

This is where the border alignment begins, which we discussed at the beginning. Since all structures must be aligned with an 8-byte boundary, and structure D is only 17 bytes, the compiler adds 5 pending bytes to the structure to make it aligned with the 8 byte boundary.

Thus, the size now becomes:

 Size of D = Size of elements of D + Padding bytes for byte alignment = 17 + 7 = 24 bytes. 
0
source

All Articles