Постоянный размер перечисления независимо от количества перечислимых значений

Почему размер enum всегда 2 или 4 байта (соответственно 16- или 32-битная архитектура), независимо от количества счетчиков в типе?

Рассматривает ли компилятор enum как union?

Ответ 1

В C и С++ размер типа enum определяется реализацией и совпадает с размером некоторого целочисленного типа.

Общий подход заключается в том, чтобы все типы enum имели тот же размер, что и int, просто потому, что обычно это тип, который обеспечивает наиболее эффективный доступ. Например, создание одного байта позволит сэкономить очень мало места, но для его доступа может потребоваться больший и медленный код, в зависимости от архитектуры процессора.

В C константы перечисления по определению имеют тип int. Итак, дано:

enum foo { zero, one, two };
enum foo obj;

выражение zero имеет тип int, но obj имеет тип enum foo, который может иметь или не иметь тот же размер, что и int. Учитывая, что константы имеют тип int, имеет тенденцию быть проще сделать перечислимый тип того же размера.

В С++ правила разные; константы имеют нумерованный тип. Но опять-таки, часто для большинства типов enum для каждого типа enum наиболее часто используется одно слово, которое обычно имеет размер int по соображениям эффективности.

И стандарт ISO С++ 2011 года добавил возможность указать базовый целочисленный тип для типа enum. Например, теперь вы можете написать:

enum foo: unsigned char { zero, one, two };

что гарантирует, что тип foo и константы zero, one и two имеют размер 1 байт. C не имеет этой функции и не поддерживается более старыми компиляторами С++ до 2011 года (если только они не предоставляют его как расширение языка).

(Далее следует отступление).

Итак, что, если у вас есть константа перечисления, слишком большая, чтобы вписаться в int? Вам не нужно 2 31 или даже 2 15 для этого нужно сделать различные константы:

#include <limits.h>
enum huge { big = INT_MAX, bigger };

Значение big равно INT_MAX, обычно 2 31 -1, но может быть как 2 15 -1 (32767). Значение bigger неявно big + 1.

В С++ это нормально; компилятор просто выберет базовый тип для huge, который будет достаточно большим, чтобы сохранить значение INT_MAX + 1. (Предположим, что существует такой тип, если int - 64 бита, и нет целого типа, большего, чем это, это будет невозможно).

В C, поскольку константы перечисления имеют тип int, вышесказанное недействительно. Он нарушает ограничение, указанное в N1570 6.7.2.2p2:

Выражение, определяющее значение константы перечисления, должно быть целочисленным постоянным выражением, которое имеет значение, представляемое как Int.

и поэтому компилятор должен отклонить его или, по крайней мере, предупредить об этом. gcc, например, говорит:

ошибка: переполнение в значениях перечисления

Ответ 2

Перечисление не является структурой, это просто способ присвоения имен целым числам. Размер переменной с этим типом - это только размер базового целочисленного типа.

Ответ 3

Размер перечисления определяется реализацией - компилятору разрешено выбирать любой желаемый размер, если он достаточно велик, чтобы соответствовать всем значениям. Некоторые компиляторы предпочитают использовать 4-байтные перечисления для всех типов перечислений, тогда как некоторые компиляторы будут выбирать наименьший тип (например, 1, 2 или 4 байта), который может соответствовать значениям перечисления. Стандарты языка C и С++ позволяют оба этих поведения.

Из C99 §6.7.2.2/4:

Каждый перечисленный тип должен быть совместим с char, целочисленным типом со знаком или беззнаковый целочисленный тип. Выбор типа определяется реализацией, 110) но должен быть способный представлять значения всех членов перечисления.

Из С++ 03 §7.2/5:

Основной тип перечисления является интегральным типом, который может представлять все значения счетчика определенных в перечислении. Определяется реализация, который является интегральным типом в качестве базового типа для перечисления, за исключением того, что базовый тип не должен превышать int, если значение счетчика не может помещаться в int или unsigned int. Если список перечислений пуст, базовый тип как если бы перечисление имело единственный перечислитель со значением 0. Значение sizeof(), применяемое к перечислению тип, объект типа перечисления или перечислитель - это значение sizeof(), применяемое к базовый тип.

Ответ 4

Мне кажется, что ОП предположил, что перечисление представляет собой некоторую коллекцию, в которой хранятся объявленные в ней значения. Это неверно.

Перечисление в C/С++ - это просто числовая переменная со строго определенным диапазоном значений. Имена перечисления - это псевдонимы для чисел.

На размер хранилища не влияет количество значений в перечислении. Размер хранилища определяется реализацией, но в основном это sizeof(int).

Ответ 5

Размер enum является "интегральным типом, по крайней мере, достаточно большим, чтобы содержать любые значения, указанные в объявлении". Многие компиляторы будут использовать int (возможно, unsigned), но некоторые будут использовать char или short, в зависимости от оптимизации или других факторов. enum с менее чем 128 возможными значениями будет соответствовать char (256 для unsigned char), и вам придется иметь 32768 (или 65536) значений для переполнения short и либо 2 или 4 миллиарда значения для перехода int в большинство современных систем.

An enum - это, по сути, просто лучший способ определить кучу разных констант. Вместо этого:

#define FIRST 0
#define SECOND 1
...

вы просто:

enum myenum
{ FIRST,
  SECOND,
  ...
};

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

Ответ 6

Большая проблема с созданием типа enum меньше int, когда меньший тип может соответствовать всем значениям, состоит в том, что он сделает ABI для единицы перевода зависящей от числа констант перечисления. Например, предположим, что у вас есть библиотека, которая использует тип enum с 256 константами как часть своего открытого интерфейса, а компилятор выбирает представление типа в виде одного байта. Теперь предположим, что вы добавляете новую функцию в библиотеку и теперь нуждаетесь в 257 константах. Компилятор должен был бы переключиться на новый размер/представление, и теперь все объектные файлы, скомпилированные для старого интерфейса, были бы несовместимы с вашей обновленной библиотекой; вам придется перекомпилировать все, чтобы он снова работал.

Таким образом, любая нормальная реализация всегда использует типы int для enum.