Разрешено ли перечисление иметь незарегистрированную стоимость?
Скажем, мы имеем
enum E
{
Foo = 0,
Bar = 1
};
Теперь мы делаем
enum E v = ( enum E ) 2;
И затем
switch ( v )
{
case Foo:
doFoo();
break;
case Bar:
doBar();
break;
default:
// Is the compiler required to honor this?
doOther();
break;
}
Так как переключатель выше обрабатывает все возможные перечисленные значения перечисления, разрешено ли компилятору оптимизировать ветвь default
выше или иначе иметь неуказанное или undefined поведение в случае, когда значение перечисления равно не в списке?
Поскольку я ожидаю, что поведение должно быть схожим для C и С++, вопрос касается обоих языков. Однако, если есть разница между C и С++ для этого случая, было бы неплохо узнать об этом.
Ответы
Ответ 1
Си ++ ситуация
В С++ каждое перечисление имеет базовый интегральный тип. Он может быть исправлен, если он явно указан (например: enum test2 : long { a,b};
) или int
по умолчанию в случае переполнения (ex: enum class test { a,b };
):
7.2/5: Каждая перечисление определяет тип, отличный от всех других типов. Каждое перечисление также имеет базовый тип. (...) если не явно заданный, тип подкатегории типа ограниченной области перечисления int. В этих случаях основной тип называется фиксированным.
В случае переполнения без доступа, где базовый тип не был явно исправлен (ваш пример), стандарт предоставляет большую гибкость вашему компилятору:
7.2/6: Для перечисления, базовый тип которого не является фиксированным, базовый тип является интегральным типом, который может представлять все значения перечисления, определенные в перечислении. (...) Определяется реализацией, какой интегральный тип используется в качестве основного типа, за исключением того, что базовый тип не должен быть больше, чем int если значение перечислителя не может быть помещено в int или без знака внутр.
Теперь очень сложная вещь: значения, которые могут удерживаться переменной перечисления, зависят от того, исправлен ли базовый тип:
-
если он исправлен, "значения перечисления являются значениями
базовый тип.
-
otherwhise, это интегральные значения в пределах минимума и максимум наименьшего битового поля, которые могут содержать наименьший счетчик и самый большой.
Вы во втором случае, хотя ваш код будет работать на большинстве компиляторов, самое маленькое битовое поле имеет размер 1, и поэтому единственными значениями, которые вы можете наверняка удерживать на всех совместимых компиляторах С++, являются значения между 0 и 1... Если вы хотите быть уверенным, что вы можете установить значение 2, вам либо нужно сделать его облачным перечислением, либо явно указать базовый тип.
Подробнее:
C ситуация
Ситуация С намного проще:
6.2.5/16: Перечисление содержит набор именованных значений целочисленной константы. Каждое отдельное перечисление представляет собой другое перечисление тип.
В принципе, это int:
6.7.2.2./2 Выражение, определяющее значение константы перечисления, должно быть целочисленным константным выражением, которое имеет значение представляемый как int.
Со следующим ограничением:
Каждый перечисленный тип должен быть совместим с char, целым числом со знаком тип или целочисленный тип без знака. Выбор типа 128), но должны быть способны представлять значения всех членов перечисления.
Ответ 2
In C a enum
type - целочисленный тип, достаточно большой для хранения всех констант enum
:
(C11, 6.7.2.2p4) "Каждый перечисленный тип должен быть совместим с char, целочисленным типом со знаком или беззнаковым целым типом. Выбор типа определяется реализацией, 110), но должен быть способен представляющие значения всех членов перечисления".
Предположим, что выбранный тип для enum E
равен _Bool
. Объект _Bool
может хранить только значения 0
и 1
. Невозможно иметь объект _Bool
, хранящий значение, отличное от 0
или 1
, без вызова поведения undefined.
В этом случае компилятору разрешено предполагать, что объект типа enum E
может содержать только 0
или 1
в строго соответствующей программе и поэтому разрешено оптимизировать случай переключения default
.
Ответ 3
С++ Std 7.2.7 [dcl.enum]:
Можно определить перечисление, которое имеет значения, не определенные ни одним из его счетчиков.
Итак, вы можете иметь значения перечисления, которые не перечислены в списке перечислителей.
Но в вашем конкретном случае "базовый тип" не является "фиксированным" (7.2.5). В спецификации не указано, что является базовым типом в этом случае, но оно должно быть целым. Поскольку char является наименьшим таким типом, мы можем заключить, что существуют другие значения перечисления, которые не указаны в списке перечислителей.
Btw, я думаю, что компилятор может оптимизировать ваш случай, когда он может определить, что нет никаких других значений, когда-либо назначенных для v, что является безопасным, но я думаю, что компиляторов пока нет.
Ответ 4
Кроме того, 7.2/10:
Выражение типа арифметики или перечисления может быть преобразовано в тип перечисления явно. Значение не изменяется, если оно находится в диапазон значений перечисления типа перечисления; в противном случае итоговое значение перечисления не указано.
Ответ 5
В C перечислениях есть тип int
. Таким образом, любое целочисленное значение может быть присвоено объекту типа перечисления.
Из стандарта C (6.7.2.2 Спецификаторы перечисления)
3 Идентификаторы в списке перечислителей объявляются как константы, которые имеют тип int и могут появляться там, где это разрешено.
В С++ перечисления имеют тип перечисления, который его определяет. В С++ вы должны либо эксплицитно указать тип подкласса, либо компилятор вычисляет максимально допустимое значение.
Из стандарта С++ (объявления 7.2 перечисления)
5 Каждая перечисление определяет тип, отличный от всех других типов. Каждое перечисление также имеет базовый тип. Основной тип может быть явно указан с использованием enum-base; если явно не указано, базовый тип типа перечисленной области - int. В этих случаях основной тип называется фиксированным. После закрывающей скобки спецификатора перечисления каждый перечислитель имеет тип перечисления.
Таким образом, в C любое возможное значение перечисления представляет собой любое целочисленное значение. Компилятор не может оптимизировать переключатель, удаляющий метку по умолчанию.
Ответ 6
В C и С++ это может работать.
Тот же код для обоих:
#include <stdio.h>
enum E
{
Foo = 0,
Bar = 1
};
int main()
{
enum E v = (enum E)2; // the cast is required for C++, but not for C
printf("v = %d\n", v);
switch (v) {
case Foo:
printf("got foo\n");
break;
case Bar:
printf("got bar\n");
break;
default:
printf("got \n", v);
break;
}
}
Тот же вывод для обоих:
v = 2
got default
В C, enum
является интегральным типом, поэтому вы можете назначить ему целочисленное значение без кастования. В С++ a enum
является его собственным типом.