Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't storage for a duplicate empty base overlap with a vtable pointer?

Tags:

c++

Consider this example:

#include <iostream>

int main()
{
    struct A {};
    struct B : A {};
    struct C : A, B {};

    std::cout << sizeof(A) << '\n'; // 1
    std::cout << sizeof(B) << '\n'; // 1
    std::cout << sizeof(C) << '\n'; // 2, because of a duplicate base

    struct E : A {virtual ~E() {}};
    struct F : A, B {virtual ~F() {}};

    std::cout << sizeof(E) << '\n'; // 8, the base overlaps the vtable pointer
    std::cout << sizeof(F) << '\n'; // 16, but why?
}

(run on godbolt)

Here you can see that for struct E the empty base class (which is 1 byte large) uses the same storage as the vtable pointer, as expected.

But for struct F, which has a duplicate empty base, this doesn't happen. What causes this?

I get the same result on GCC, Clang, and MSVC. The results above are for x64, so sizeof(void *) == 8.


Interestingly, for struct G : A, B {void *ptr;}; GCC and Clang do perform EBO (the size is 8), but MSVC doesn't (the size is 16).

like image 458
HolyBlackCat Avatar asked Nov 15 '19 14:11

HolyBlackCat


1 Answers

Because the compiler adds one byte padding after struct A

F {vptr(8) + 0 members from A + 1 padding (because A is empty)+0 from b} = 9 then the compiler add 7 bytes padding to align storage of the struct;

E {vptr(8) + 0 members for A} = 8 No padding required

from Microsoft

Every data object has an alignment-requirement. For structures, the requirement is the largest of its members. Every object is allocated an offset so that offset % alignment-requirement == 0

https://docs.microsoft.com/en-us/cpp/c-language/storage-and-alignment-of-structures?view=vs-2019

EDIT:

here is my demo:

int main()
{
    C c;
    A* a = &c;
    B* b = &c;

    std::cout << sizeof(A) << " " << a << '\n'; 
    std::cout << sizeof(B) << " " << b << '\n'; 
    std::cout << sizeof(C) << " " << &c << '\n'; 

    E e;
    a = &e;
    std::cout << sizeof(E) <<" " << &e << " " << a << '\n'; 

    F f;
    a = &f;
    b = &f;
    std::cout << sizeof(F) << " " << &f << " " << a << " " << b << '\n';

}

output:

1 0000007A45B7FBB4
1 0000007A45B7FBB5
1 0000007A45B7FBB4
8 0000007A45B7FC18 0000007A45B7FC20
16 0000007A45B7FC38 0000007A45B7FC40 0000007A45B7FC41

as you can see a & b never overlaps with each other and with vptr on multiple inheritance each has its own pointer value

note compiled by VC2019 x64 build

like image 51
Ahmed Anter Avatar answered Nov 04 '22 15:11

Ahmed Anter