В C++ почему производный класс, который просто содержит объединение с экземпляром своего базового класса, занимает больше памяти, чем размер объединения?

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

следующий код иллюстрирует мой вопрос:

#include <iostream>

class empty_class { };

struct big : public empty_class
{
    union
    {
        int data[3];
        empty_class a;
    };
};

struct small
{
    union
    {
        int data[3];
        empty_class a;
    };
};

int main()
{   
    std::cout << sizeof(empty_class) << std::endl;
    std::cout << sizeof(big)         << std::endl;
    std::cout << sizeof(small)       << std::endl;
}

вывод этого кода при компиляции с использованием gcc версии 7.3.0, скомпилированной с -std=c++17 (хотя я получаю тот же результат, используя c++11 и C++14), является:

1
16
12

Я бы ожидал, что классы большой и маленький должно быть того же размера; как ни странно,большой занимает больше памяти, чем маленький хотя они оба, по-видимому, содержат одни и те же данные.

также, Даже если размер массива в объединении изменен, разница между размером большой и маленький постоянная 4 байты.

-Edit:

похоже, что это поведение не относится к классам с типами данных объединения. Подобное поведение происходит в других подобных ситуациях, когда производный класс имеет член с типом базового класса. Спасибо тем, кто указал на это.

2 ответов


это из-за того, что я называю "уникальным правилом идентификации" C++. Каждый (живой) объект в C++ определенного типа T всегда имеют другой адрес от любого другого живого объекта типа T. Компилятор не может предоставить макет для типа, где это правило будет нарушено, где два различных подобъекта с одним и тем же типом T будет иметь такое же смещение в макете их содержащего объекта.

класс big содержит два подобъекты Примечание: базовый класс empty_class и анонимный Союз, содержащий члена empty_class.

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

если компилятор дал базовый класс empty_class тот же адрес, что и у Союза member, тогда у вас будет два различных подобъекта класса (big::empty_class и big::a), которые имеют один и тот же адрес, но являются разными объектами.

такой макет нарушит уникальное правило идентификации. И поэтому компилятор не может используйте здесь пустую базовую оптимизацию. Вот почему big - это не стандартный формат.


на union красный селедки.

если упростить до

struct empty{};

struct big : empty
{
    empty e;
};

затем sizeof(big) должны больше, чем sizeof(empty). Это потому, что есть два объекта типа empty на big и поэтому они требуют разных адресов. The пустая базовая оптимизация не может быть применен здесь, как это может быть для

struct small : empty
{
    int n;
};

где вы могли бы ожидать sizeof(small) на sizeof(int).