Чому сховище для дубліката порожньої бази не перетинається з vtable pointer?


11

Розглянемо цей приклад:

#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?
}

(бігати на бобі)

Тут ви бачите, що для struct Eпорожнього базового класу (який на 1 байт великий) використовується те саме сховище, що і покажчик vtable, як і очікувалося.

Але для тих struct F, хто має дублікат порожньої бази, цього не відбувається. Що це викликає?

Я отримую такий же результат на GCC, Clang та MSVC. Наведені вище результати для x64, так sizeof(void *) == 8.


Цікаво, що для struct G : A, B {void *ptr;};GCC та Clang роблять EBO (розмір - 8), але MSVC - ні (розмір - 16).


3
Як не дивно, успадкувавши від C(які успадковують від A, B) ви отримаєте інший результат , ніж спадкування форми Aі Bбезпосередньо
Гійом Racicot

1
Мені подобалося досліджувати це. Дякуємо за запитання та посилання. Я не впевнений, що у мене є відповідь, і тому просто прокоментую. Можливо, це випливає з неоднозначності, внесеної внаслідок походження Cта F? Зрештою, 2 * sizeof(void*) == 16на x86_64, як ви вже говорили. Компілятор не може повністю оптимізувати (як сказав Story Teller), і так не робить.
Ендрю Фаланга

2
Це нормально, що ви отримуєте однаковий результат на gcc та clang, оскільки вони обидва слідують за ітанієвим ABI. І якщо це так, я думаю, коли визначали ABI, вони боялися, що алгоритм компонування може стати надто дорогим, тому вони взяли деякі ярлики (ака песимізація).
Марк

2
@RianQuinn Дублікат бази не робить структуру недійсною.
HolyBlackCat

1
@RianQuinn успадковуючи від одного і того ж класу кілька разів через різні "контури", цілком справедливо для C ++. Якщо ви хочете створити алмазну структуру, тобто мати базовий клас лише один раз, вам доведеться використовувати віртуальне успадкування. Але якщо ви не хочете мати алмаз, а наявність дублікату базового класу для вас не є проблемою, то для мови це також не є проблемою. Код ОП створює лише попередження, кажучи, що другий A, успадкований через B, не може бути доступний. Це добре. Тільки якщо ви насправді намагаєтесь отримати доступ до нього, як у вашому прикладі, ви отримуєте помилку.
sebrockm

Відповіді:


4

Тому що компілятор додає один байт-прокладки після Stru A

F {vptr (8) + 0 членів з підкладки A + 1 (оскільки A порожній) +0 від b} = 9, тоді компілятор додає 7 байтів, щоб вирівняти зберігання структури;

E {vptr (8) + 0 членів для A} = 8 Не потрібна прокладка

від Microsoft

Кожен об'єкт даних має вимогу вирівнювання. Для структур вимога є найбільшою з її членів. Кожному об'єкту присвоюється зміщення, так що зміщення% вирівнювання-вимога == 0

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

Редагувати:

ось моя демонстрація:

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';

}

вихід:

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

як ви можете бачити & b ніколи не перетинається один з одним, і з vptr при множинному успадкуванні кожен має своє значення вказівника

примітка, складена VC2019 x64 build


Я не думаю, що це так працює. Незважаючи на те, що Aне має членів, він все ще займає 1 байт (яким можна поділитися з іншим об'єктом). В E, Aне розташований після vptr; він перекриває перший байт vptr. (Ось демонстрація ; я трохи змінив код, щоб зробити Aдоступним.) Те саме відбувається і для першого Aв F. Оскільки AB) можна розмістити поверх vptr, я не впевнений, чому це не відбувається B.
HolyBlackCat

@HolyBlackCat, але ось що сталося перевірити тестовий код
Ахмед Антер

Так, MSVC поводиться інакше, ніж GCC / Clang тут; це недостатньо розумно, щоб поставити Aповерх vptr. Це може пояснити, чому на MSVC вихід 16, але я не впевнений, що відбувається з GCC & Clang.
HolyBlackCat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.