Подтвердить что ты не робот

Разрешено ли перечисление иметь незарегистрированную стоимость?

Скажем, мы имеем

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 и С++ для этого случая, было бы неплохо узнать об этом.

4b9b3361

Ответ 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 является его собственным типом.