Почему sizeof(std::variant) имеет тот же размер, что и структура с теми же элементами?

Шаблон класса std::variant представляет типобезопасное объединение. Экземпляр std::variant в любой момент времени либо содержит значение одного из своих альтернативных типов, либо не имеет значения.

sizeof(std::variant<float, int32_t, double>) == 16

Но если это союз, то почему он занимает так много места?

struct T1 {
    float a;
    int32_t b;
    double c;
};

struct T2 {
    union {
        float a;
        int32_t b;
        double c;
    };
};

Вариант имеет тот же размер, что и структура

sizeof(T1) == 16
sizeof(T2) == 8

Я бы ожидал, что размер объединения плюс 4 байта для хранения, какой тип активен.


person Iter Ator    schedule 08.08.2017    source источник
comment
У вас 32-битная система или 64-битная? Какой компилятор/стандартную библиотеку вы используете?   -  person Justin    schedule 08.08.2017
comment
32-разрядная версия и Visual Studio с параметром /std:c++latest   -  person Iter Ator    schedule 08.08.2017
comment
даже если это так, это займет 16 байтов, если это стандартная компоновка. поскольку самое большое слово в вашей структуре будет 8 байтов, в вашем случае 4 байта будут дополнены до 8. удалите двойное число, и его размер будет равен 8.   -  person Swift - Friday Pie    schedule 08.08.2017
comment
Стечение обстоятельств. Добавьте больше участников.   -  person Pete Becker    schedule 08.08.2017


Ответы (4)


Здесь тип с наибольшим выравниванием в variant — это double с выравниванием 8. Это означает, что весь variant должен иметь выравнивание 8. Это также означает, что его размер должен быть кратным 8, гарантируя, что в массиве каждый variant будет иметь его double, выровненный по 8.

Но вариант должен хранить больше, чем просто самый большой тип: он также должен хранить идентификатор или тег, чтобы знать, какой тип в настоящее время создан. Таким образом, даже если для этого идентификатора используется только 1 байт, структура в целом дополняется до 16, так что ее размер кратен 8.

Более корректным было бы сравнение:

struct
{
    union
    {
        float a;
        int32_t b;
        double c;
    };
    int identifier;
};
person Cory Nelson    schedule 08.08.2017
comment
Стоит отметить, что, по крайней мере, в некоторых системах накладные расходы составляют всего один байт (по крайней мере, если вариантов мало). На этой машине sizeof(std::variant<char, char[1000], char[2000]>) == 2001. - person Daniel H; 09.08.2017
comment
@DanielH Наибольшее выравнивание всех ваших типов - 1, что позволяет identifier, занимающему только один байт, не иметь заполнения. Сделайте один из них int, и вы увидите отступы. Вы демонстрируете деталь реализации stdlib, а не деталь машины. - person Cory Nelson; 09.08.2017
comment
Да; моя точка зрения заключалась не в том, что не будет отступов или чего-то вроде шляпы, а в том, что с такими большими типами ясно, что хранилище было повторно использовано, иначе вам потребуется 3002 байта для всего. И я полагаю, вы правы в том, что я сослался не на ту часть реализации; Я хотел сказать, что это особенность этой реализации. - person Daniel H; 09.08.2017

Основной ответ: по причинам выравнивания. Поскольку один из ваших типов является двойным, а двойные числа имеют размер 8 байтов с требованиями к выравниванию по 8 байтам, это означает, что вариант также имеет требование к выравниванию по 8 байтам. Таким образом, он может быть только кратен 8 байтам. Как вы сами заметили, минимальный размер — это самый большой тип + что-то дополнительное, чтобы указать, какой член активен. Это означает, что он не может уместиться в 8 байт, но тогда требования выравнивания вынуждают его увеличиваться до 16 байт.

Именно поэтому он не может быть 12 байт, как вы рассуждаете. Кстати, также нет правила, согласно которому объем памяти активного типа должен быть 4 байта. На самом деле совершенно очевидно, что в подавляющем большинстве случаев можно обойтись одним байтом, который может различать 255 типов плюс пустое состояние. Мы можем проверить это:

struct null {};
std::cerr << sizeof(std::variant<bool, null>);

Я только что проверил это на coliru, и он напечатал «2». В данном случае 1 байт для самого большого типа (bool) и 1 байт для определения того, какой тип активен.

person Nir Friedman    schedule 08.08.2017
comment
sizeof(null) также будет 1 байтом, а не 0. - person Ajay; 01.06.2018

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

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

person SergeyA    schedule 08.08.2017
comment
Извините, что снова придираюсь к вам сегодня, но хотя этот ответ технически правильный, лучший из правильных и, таким образом, кажется, одобренный языковыми юристами, на самом деле он не отвечает на вопрос. разработчики стандартных библиотек очень умны и не стали бы тратить место без причины. Настоящая причина - выравнивание. - person Nir Friedman; 08.08.2017
comment
@NirFriedman, и что я сказал? Размер больше из-за заполнения (для целей выравнивания) и необходимости удерживать дополнительный элемент. - person SergeyA; 08.08.2017
comment
Что ж, вы не упоминаете слово «выравнивание» (которое является самым важным словом), и, кроме того, я не думаю, что из него ясно, что размер варианта, который упоминает ОП, на самом деле является минимально возможным. - person Nir Friedman; 08.08.2017
comment
@NirFriedman, отступы и выравнивание относятся друг к другу, как бургер к кетчупу. Если вы ищете отступы: google.com/ самый верхний результат: data structure alignment - person SergeyA; 08.08.2017

Как ни странно, вас обманули по стечению обстоятельств. Возврат следующего равен 16:

sizeof(std::variant<float, int32_t, double, int64_t>)

И это тоже:

sizeof(std::variant<float, int32_t, double, int64_t, double>)

Таким образом, в std::variant есть внутренняя переменная размером 8 байт (или меньше, но выровненная по 8 байтам). Это, в дополнение к вашему союзу, дает 16.

person The Quantum Physicist    schedule 08.08.2017
comment
Маловероятно, что эта внутренняя переменная будет иметь размер 8 байт. Это из соображений выравнивания. - person Nir Friedman; 08.08.2017
comment
@NirFriedman Конечно. Просто он резервирует эту сумму. Я поправлю свое утверждение. - person The Quantum Physicist; 08.08.2017
comment
зачем гадать, если можно посмотреть в заголовке. я многому научился (о плохом), читая заголовки стандартной библиотеки. Это, конечно, определяется реализацией - person Swift - Friday Pie; 08.08.2017