Константы Enum ведут себя по-разному в C и С++

Почему это:

#include <stdio.h>
#include <limits.h>
#include <inttypes.h>

int main() {
    enum en_e {
        en_e_foo,
        en_e_bar = UINT64_MAX,
    };
    enum en_e e = en_e_foo;
    printf("%zu\n", sizeof en_e_foo);
    printf("%zu\n", sizeof en_e_bar);
    printf("%zu\n", sizeof e);
}

напечатать 4 8 8 в C и 8 8 8 в С++ (на платформе с 4 байтовыми ints)?

У меня создалось впечатление, что назначение UINT64_MAX заставит все константы перечислений иметь как минимум 64 бита, но en_e_foo остается равным 32 в простой C.

В чем причина несоответствия?

Ответ 1

Я взглянул на стандарты, и моя программа оказалась нарушением ограничения в C из-за 6.7.2.2p2:

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

и определен в С++ из-за 7.2.5:

Если базовый тип не является фиксированным, тип каждого перечислителя тип его инициализирующего значения: - Если указан инициализатор для перечислителя значение инициализации имеет тот же тип, что и выражение и константное выражение должны быть интегральной константой выражение (5.19). - Если для первого указателя не указан перечислитель, значение инициализации имеет неопределенный интегральный тип. - В противном случае тип инициализирующего значения будет таким же, как тип от инициализирующего значения предыдущего перечислителя, если только добавочное значение не представляется в этом типе, и в этом случае тип является неопределенным интегральным типом, достаточным для увеличенное значение. Если такой тип не существует, программа плохо сформирована.

Ответ 2

В C константа enum имеет тип int. В С++ это перечисляемый тип.

enum en_e{
    en_e_foo,
    en_e_bar=UINT64_MAX,
};

В C это нарушение ограничения, требующее диагностики (если UINT64_MAX превышает INT_MAX, что очень вероятно). Компилятор C может вообще отказаться от программы или может напечатать предупреждение, а затем сгенерировать исполняемый файл, поведение которого undefined. (Не на 100% ясно, что программа, которая нарушает ограничение, обязательно имеет поведение undefined, но в этом случае стандарт не говорит, что такое поведение, так что все еще undefined поведение.)

gcc 6.2 не предупреждает об этом. clang делает. Это ошибка в gcc; он неправильно блокирует некоторые диагностические сообщения, когда используются макросы из стандартных заголовков. Спасибо Grzegorz Szpetkowski за поиск отчета об ошибке: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71613

В С++ каждый тип перечисления имеет базовый тип, который представляет собой некоторый целочисленный тип (не обязательно int). Этот базовый тип должен иметь возможность представлять все постоянные значения. Таким образом, в этом случае как en_e_foo, так и en_e_bar имеют тип en_e, который должен иметь ширину не менее 64 бит, даже если int уже.

Ответ 3

В C, тогда как a enum рассматривается как отдельный тип, у самих счетчиков всегда есть тип int.

C11 - 6.7.2.2 Спецификаторы перечисления

3 Идентификаторы в списке перечислений объявляются как константы с типом int...

Таким образом, поведение, которое вы видите, является расширением компилятора.

Я бы сказал, что имеет смысл только расширить размер одного из счетчиков, если его значение слишком велико.


С другой стороны, в С++ все счетчики имеют тип enum, в котором они объявлены.

Из-за этого размер каждого перечислителя должен быть таким же. Таким образом, размер целых enum расширяется для хранения самого большого счетчика.

Ответ 4

Этот код просто недействителен C в первую очередь.

В разделе 6.7.2.2 как в C99, так и в C11 говорится, что:

Ограничения:

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

Диагностика компилятора обязательна, поскольку это нарушение ограничений, см. 5.1.1.3:

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

Ответ 5

Как указывали другие, код плохо сформирован (в C) из-за нарушения ограничений.

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

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

Текущее обходное решение может заключаться в том, чтобы добавить макрос с унарным оператором +:

enum en_e {
   en_e_foo,
   en_e_bar = +UINT64_MAX,
};

что дает ошибку компиляции на моей машине с помощью GCC 4.9.2:

$ gcc -std=c11 -pedantic-errors -Wall main.c 
main.c: In function ‘main’:
main.c:9:20: error: ISO C restricts enumerator values to range of ‘int’ [-Wpedantic]
         en_e_bar = +UINT64_MAX

Ответ 6

C11 - 6.7.2.2/2

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

en_e_bar=UINT64_MAX является нарушением ограничения, и это делает вышеуказанный код недействительным. Диагностическое сообщение должно быть получено путем подтверждения реализации, как указано в проекте C11:

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

Кажется, что у GCC есть ошибка, и он не смог создать диагностическое сообщение. (Ошибка указана в ответе Гжегож Сппетковски