Почему хранилище для дубликата пустой базы не перекрывается указателем vtable?


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, как вы сказали. Компилятор не может полностью оптимизировать (как сказал Стори Теллер) и не делает этого.
Андрей Фаланга

2
Это нормально, что вы получаете один и тот же результат на gcc и clang, так как они оба следуют ITI ABI. И если это так, я думаю, что при определении ABI они боялись, что алгоритм компоновки может стать слишком дорогим, поэтому они взяли несколько ярлыков (иначе пессимизации).
Марк

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

1
@RianQuinn, наследующий от одного и того же класса несколько раз по разным «путям», вполне допустим в C ++. Если вы хотите создать алмазную структуру, т.е. иметь базовый класс только один раз, вы должны использовать виртуальное наследование. Но если вам не нужен бриллиант, и наличие дублирующего базового класса для вас не проблема, то и для языка это тоже не проблема. Код OP генерирует только предупреждение о том, что второй A, унаследованный через B, не может быть доступен. Это хорошо. Только если вы действительно попытаетесь получить к нему доступ, как в вашем примере, вы получите ошибку.
Sebrockm

Ответы:


4

Потому что компилятор добавляет один байт после структуры A

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

E {vptr (8) + 0 членов для A} = 8 Заполнение не требуется

от Microsoft

Каждый объект данных имеет требование выравнивания. Для структур, требование является крупнейшим из его членов. Каждому объекту назначается смещение, так что смещение% alignment-demand == 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

как вы можете видеть, a & 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.