Почему sizeof (Derived4) равен 8 байтам? Я думаю, что это должно быть 5 байтов

Это вывод данной программы:

sizeof(Empty) 1
sizeof(Derived1) 1
sizeof(Derived2) 4
sizeof(Derived3) 1
sizeof(Derived4) 8
sizeof(Dummy) 1

Это программа:

#include <iostream>
using namespace std;

class Empty
{};

class Derived1 : public Empty
{};

class Derived2 : virtual public Empty
{};

class Derived3 : public Empty
{
char c;
};

class Derived4 : virtual public Empty
{
char c;
};

class Dummy
{
char c;
};

int main()
{
cout << "sizeof(Empty) " << sizeof(Empty) << endl;
cout << "sizeof(Derived1) " << sizeof(Derived1) << endl;
cout << "sizeof(Derived2) " << sizeof(Derived2) << endl;
cout << "sizeof(Derived3) " << sizeof(Derived3) << endl;
cout << "sizeof(Derived4) " << sizeof(Derived4) << endl;
cout << "sizeof(Dummy) " << sizeof(Dummy) << endl;

return 0;
}

Размер Derived3 составляет 1 байт. Тогда почему размер Derived 4 составляет 8 байт ?? Если выравнивание является ответом, то почему нет выравнивания в случае производного3?

3

Решение

Это зависит от выравнивания членов данных в классе. Кажется, что если класс имеет виртуальный базовый класс, то его реализация содержит ссылку на этот виртуальный базовый класс, который в вашем случае равен 4 байта. Когда вы добавляете элемент данных типа char, он дополняется тремя байтами, чтобы обеспечить выравнивание для ссылки на базовый виртуальный класс.

5

Другие решения

Для конкретный тип T, sizeof означает две вещи:

  • представление завершить объект a типа T занимает только sizeof(T) байт в [(char*)&a, (char*)&a + sizeof(T));

  • массив из T хранит второй объект sizeof(T) после первого.

Байты, занятые законченными объектами, не перекрываются: либо один является субъектом другого и содержится в нем, либо они не имеют общих байтов.

Вы можете перезаписать полный объект (с memset) и затем используйте размещение new, чтобы восстановить его (или просто присваивание объектам без осмысленного построения), и все будет хорошо, если деструктор не был важен (не делайте этого, если деструктор отвечает за освобождение ресурса) , Вы не можете перезаписать только подобъект базового класса, так как он разрушит весь объект. sizeof говорит вам, сколько байтов вы можете перезаписать, не разрушая другие объекты.

Элементы данных класса являются полными объектами, поэтому размер класса всегда равен сумме размеров его членов.

Некоторые типы являются «полными»: каждый бит в объекте имеет смысл; в частности, unsigned char, Некоторые типы имеют неиспользуемые биты или байты. У многих классов есть такие «дыры» для заполнения. Пустой класс имеет нулевой значащий бит: ни один бит не является частью состояния, так как его нет. Пустой класс — это конкретный класс, но он создан; каждый экземпляр имеет идентичность, следовательно, отдельный адрес, поэтому его размер не может быть нулевым, даже если стандарт допускает нулевые значения sizeof, Пустой класс — это чистый отступ.

Рассматривать:

struct intchar {
int i;
char c;
};

Выравнивание intchar это выравнивание int, На типичной системе, где sizeof(int) равно 4 и выравнивание этих основных типов равно размеру,
так intchar имеет выравнивание 4 и размер 8, потому что размер соответствует расстоянию между двумя элементами массива, поэтому 3 байта не используются для представления.

Дано intchar_char

struct intchar_char {
intchar ic;
char c;
};

размер должен быть больше размера intchar даже с неиспользованными байтами существуют в ic из-за выравнивания: член ic является законченным объектом и занимает все его байты, и memset разрешено в этом объекте.

sizeof хорошо определен только для конкретных типов (которые могут быть созданы) и завершенных объектов. Так вам нужно sizeof определить размер пустого класса, если вы хотите создать массивы таких; но для подобъекта базового класса, sizeof не дает вам информацию, которую вы хотите.

В C ++ нет оператора для измерения количества байтов, используемых в представлении класса, но вы можете попробовать с производным классом:

template <class Base, int c=1>
struct add_chars : Base {
char dummy[c];
};

template <class T>
struct has_trailing_unused_space {
static const bool result = sizeof (add_chars<T>) == sizeof (T);
};

Обратите внимание, что add_chars<T> не имеет члена типа Tтак нет T завершить объект и memset не допускается на intchar субобъект. dummy является законченным объектом, который не может перекрываться с любым другим завершенным объектом, но может перекрываться с подобъектом базового класса.

Размер производного класса не всегда является, по крайней мере, суммой размеров его объектов.

Член dummy занимает ровно один байт; если есть какой-либо завершающий байт в Baseбольшинство компиляторов будет выделять dummy в неиспользованном пространстве; has_trailing_unused_space проверяет это свойство.

int main() {
std::cout << "empty has trailing space: ";
std::cout << has_trailing_unused_space<empty>::result;
}

выходы:

пустое место в конце: 1

виртуальное наследство

При рассмотрении расположения классов, включающих виртуальные функции и классы виртуальных баз, вам необходимо учитывать скрытый vptr и внутренние указатели. Они будут иметь те же свойства (размер и выравнивание), что и void* в типичных реализациях.

class Derived2 : virtual public Empty
{};

В отличие от обычного наследования и членства, виртуальное наследование не определяет строгое, прямое отношение владения, но общее косвенное владение, подобно вызову виртуальной функции, вызывает косвенное обращение. Виртуальное наследование создает два вида макетов классов: подобъекты базового класса и полные макеты объектов.

Когда создается экземпляр класса, компилятор будет использовать макет, определенный для завершенных объектов, что может быть с использованием vptr, как это делает GCC, а Titanium ABI предусматривает:

struct Derived2 {
void *__vptr;
};

Vptr указывает на полную vtable со всей информацией о времени выполнения, но язык C ++ не считает такой класс полиморфным классом, поэтому dynamic_cast/typeid не может быть использован для определения динамического типа.

AFAIK, Visual C ++ использует не vptr, а указатель на подобъект:

struct Derived2 {
Empty *__ptr;
};

И другие компиляторы могут использовать относительное смещение:

struct Derived2 {
offset_t __off;
};

Derived2 очень простой класс; макет подобъекта Derived2 такой же, как и полная компоновка объекта.

Не рассматривайте немного более сложный случай:

struct Base {
int i;
};

struct DerV : virtual Base {
int j;
};

Здесь полный макет DerV может быть (в стиле Titanium ABI):

struct complete__DerV {
void *__vptr;
int j;
Base __base;
};

Макет подобъекта

struct DerV {
void *__vptr;
int j;
};

Все полные или неполные объекты типа DerV есть этот макет.

Vtable содержит относительные смещения виртуальной базы: offsetof(complete__DerV,__base) в случае объекта динамического типа DerV,

Вызов виртуальной функции может быть выполнен путем поиска переопределителя во время выполнения или знания динамического типа по правилам языка.

Upcast (преобразование указателя в виртуальный базовый класс), что часто происходит неявно, когда функция-член вызывается для базового класса:

struct Base {
void f();
};

struct DerV : virtual Base {
};

DerV d;
d.f(); // involves a derived to base conversion

либо использует известное смещение, когда известен динамический тип, как здесь, либо использует информацию времени выполнения для определения смещения:

void foo (DerV &d) {
d.f(); // involves a derived to base conversion
}

можно перевести на (Titanium ABI-style)

void foo (DerV &d) {
(Base*)((char*)&d + d.__vptr.off__Base)->f();
}

или Visual C ++ — стиль:

void foo (DerV &d) {
d.__ptr->f();
}

или даже

void foo (DerV &d) {
(Base*)((char*)&d + d.__off)->f();
}

Издержки зависят от реализации, но они присутствуют всякий раз, когда динамический тип неизвестен.

0

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector