Проблема размера объекта наследования виртуального класса

Здесь, в этом коде, размер ob1 равен 16, что нормально (из-за виртуального указателя), но я не могу понять, почему размер ob2 равен 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";
}

Я ожидаю размер ob2 как 20, но на выходе 24


person Srijib Bose    schedule 13.08.2019    source источник
comment
Механизм virtual требует некоторых накладных расходов. Некоторые компиляторы помещают эти накладные расходы в структуру.   -  person Thomas Matthews    schedule 13.08.2019
comment
Вы должны иметь привычку всегда завершать свой вывод либо '\n', либо std::endl.   -  person Thomas Matthews    schedule 13.08.2019
comment
Обычно с виртуальным наследованием вводится указатель vtable. Это может объяснить разницу в размерах,   -  person πάντα ῥεῖ    schedule 13.08.2019
comment
@πάνταῥεῖ: обычно есть указатель vtable . Вводит ли виртуальное наследование указатель vtable? Я не верю!   -  person Klaus    schedule 13.08.2019
comment
Я ожидаю, что размер ob2 будет равен 20, но на выходе будет 24 Какая у вас точная платформа? Вероятно, что-то внутри объекта требует 8-байтового выравнивания. Или вообще сама платформа накладывает требование 8-байтового выравнивания.   -  person Andrew Henle    schedule 13.08.2019
comment
@Клаус Да. для виртуального наследования требуется некоторый механизм времени выполнения, обычно реализуемый с использованием виртуальной таблицы. Например, vtable может содержать указатель на экземпляр базового класса.   -  person Guillaume Racicot    schedule 13.08.2019
comment
@AndrewHenle Я тоже так думал, но не могу определить точную причину 8-байтового выравнивания. Я использую Ubuntu 64-бит   -  person Srijib Bose    schedule 13.08.2019
comment
@GuillaumeRacicot: я также нашел здесь статью: en.wikipedia.org/wiki/Virtual_inheritance, которая говорит Однако это смещение в общем случае может быть известно только во время выполнения, .... Я не понял, какая информация нужна и известна только во время выполнения.   -  person Klaus    schedule 13.08.2019
comment
@πάνταῥεῖ Да. Вот почему я получил 16 для ob1, так как размер указателя vtable равен 8. Но по той же логике он должен быть 20 для ob2   -  person Srijib Bose    schedule 13.08.2019
comment
@Klaus при использовании виртуального наследования смещение базового класса известно только во время выполнения, поскольку оно может меняться в зависимости от типа класса, расположенного ниже в иерархии.   -  person Guillaume Racicot    schedule 13.08.2019
comment
@GuillaumeRacicot Обычно здесь слишком силен; существуют очень разные детали реализации re: VI в отличие от виртуальных функций и одиночного наследования, которое чрезвычайно единообразно (хотя и не до точки двоичной совместимости) среди компиляторов. MSVC++ и GCC имеют очень разные подходы. Кроме того, по определению vtables никогда не содержат ptr для экземпляров пользовательского класса.   -  person curiousguy    schedule 19.09.2019
comment
Для такого Q, как правило, хорошей идеей будет распечатать размер различных соответствующих скалярных типов, чтобы у нас было хорошее представление о размере базовых типов на вашей платформе и реализации C++, которые могут сильно различаться между популярными реализациями. Их можно догадаться по размеру составных типов, но проще этого не делать.   -  person curiousguy    schedule 19.09.2019
comment
@ThomasMatthews Некоторые компиляторы помещают эти накладные расходы в структуру. Только некоторые? Какие нет?   -  person curiousguy    schedule 20.09.2019
comment
@curiousguy: существует множество компиляторов, и я не знаю, как работает все из них. Согласно стандарту компилятору не требуется использовать виртуальные таблицы. Кстати, существует мир встроенных систем, и его компиляторы могут работать иначе, чем компиляторы для настольных компьютеров (поскольку многие встроенные системы ограничены в ресурсах).   -  person Thomas Matthews    schedule 20.09.2019
comment
@ThomasMatthews Действительно, но использование vptr для реализации виртуальных вещей по своей сути является очень эффективным способом получения информации, он локальный, имеет ограниченное время и ограниченное пространство и не требует глобального изменяемого состояния. Альтернативы могут быть составлены, но они в основном требуют изменяемого глобального состояния, что означает блокировку повсюду, если вы хотите поддерживать MT, а также непостоянное время для многих операций. И это неэффективно для создания недолговечных полиморфных объектов (даже если полиморфизм времени выполнения не используется). Использование vtables — это не только вопрос удобства и совместимости с ABI, но и вопрос эффективности.   -  person curiousguy    schedule 20.09.2019
comment
@curiousguy, Чтобы ответить на ваш вопрос, какие из них не работают?, не могли бы вы выполнить некоторые метрики для всех компиляторов? Вот некоторые из них: Keil, IAR, Visual Studio, G++, CLang, MinGW, Intel, ARM, Greenhills и Metaware и многие другие.   -  person Thomas Matthews    schedule 21.09.2019
comment
@ThomasMatthews Сколько из этих компиляторов не утверждают, что следуют хорошо известному ABI? Все известные C++ ABI основаны на vtable. (Конечно, существуют большие различия в мелких деталях представления vtable.) Если компилятор не предназначен для двоичной совместимости с другими компиляторами, он должен использовать vtables.   -  person curiousguy    schedule 21.09.2019


Ответы (2)


Один из возможных макетов для объектов типа D:

+----------+
| y        |   The B subobject (8 bytes)
| z        |
+----------+
| vptr     |   vtable pointer (8 bytes)
|          |
+----------+
| b        |   4 bytes
+----------+
| unused   |   4 bytes (padding for alignment purposes)
+----------+

Получается sizeof(ob2) 24.

Требования к выравниванию определяются реализацией. В большинстве случаев размер самого большого объекта-члена или подобъекта определяет требования к выравниванию объекта. В вашем случае размер самого большого объекта, указателя vtable, составляет 8 байт. Следовательно, реализация выравнивает объекты по 8-битным границам, добавляя заполнение при необходимости.

person R Sahu    schedule 13.08.2019
comment
Очевидно правильный ответ. Возможно, вы можете уточнить бит for alignment purposes? - person Walter; 13.08.2019
comment
Меня интересует вопрос: зачем нам в этом случае нужен указатель vtable во время выполнения. Я не могу понять, почему здесь это зависит от времени выполнения, поскольку иерархия наследования полностью известна во время компиляции. Спасибо... может еще вопрос :-) - person Klaus; 13.08.2019
comment
@Клаус, да, это другой вопрос. - person R Sahu; 13.08.2019
comment
@Klaus Отношения наследования известны во время компиляции не более, чем члены функций. Виртуальные вещи могут быть переопределены в производных классах, в отличие от не виртуальных вещей. - person curiousguy; 19.09.2019

Для реализации виртуального наследования D содержит в качестве члена данных указатель, который в 64-битной системе требует 8 байтов. Более того, эти 8 байт должны быть выровнены по 8-байтовой границе памяти. Это последнее требование, в свою очередь, требует, чтобы D было выровнено по 8-байтовой границе памяти. Самый простой способ реализовать это — сделать sizeof(D) кратным 8, дополнив его неиспользуемыми байтами (21-24).

person Walter    schedule 13.08.2019
comment
заполняет его неиспользуемыми байтами, и эту (почти наверняка правильную) гипотезу можно проверить, добавив фиктивный член данных в конце, размер предполагаемого заполнения и проверив, что общий размер не меняется. - person curiousguy; 20.09.2019