Enum constants behaving differently in C and C++

Why does this:

#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);
}

print 4 8 8 in C and 8 8 8 in C++ (on a platform with 4 byte ints)?

I was under the impression that the UINT64_MAX assignment would force all the enumerations constants to at least 64 bits, but en_e_foo remains at 32 in plain C.

What is the rationale for the discrepancy?

5036 次浏览

C11 - 6.7.2.2/2

The expression that defines the value of an enumeration constant shall be an integer constant expression that has a value representable as an int.

en_e_bar=UINT64_MAX is a constraint violation and this makes the above code invalid. A diagnostic message should be produce by confirming implementation as stated in the C11 draft:

A conforming implementation shall produce at least one diagnostic message (identified in an implementation-defined manner) if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint, [...]

It seems that GCC has some bug and it failed to produce the diagnostic message. (Bug is pointed in the answer by Grzegorz Szpetkowski

In C, while a enum is considered to be a separate type, enumerators itself always have type int.

C11 - 6.7.2.2 Enumeration specifiers

3 The identifiers in an enumerator list are declared as constants that have type int...

Thus, behaviour you see is a compiler extension.

I'd say it makes sense to only expand size of one of the enumerators if its value is too large.


On the other hand, in C++ all enumerators have the type of the enum they're declared in.

Because of that, size of every enumerator must be same. So, size of entire enum is expanded to store the largest enumerator.

That code just isn't valid C in the first place.

Section 6.7.2.2 in both C99 and C11 says that:

Constraints:

The expression that defines the value of an enumeration constant shall be an integer constant expression that has a value representable as an int.

A compiler diagnostic is mandatory because it is a constraint violation, see 5.1.1.3:

A conforming implementation shall produce at least one diagnostic message (identified in an implementation-defined manner) if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint, even if the behavior is also explicitly specified as undefined or implementation-defined.

I took a look at the standards and my program appears to be a constraint violation in C because of 6.7.2.2p2:

Constraints: The expression that defines the value of an enumeration constant shall be an integer constant expression that has a value representable as an int.

and defined in C++ because of 7.2.5:

If the underlying type is not fixed, the type of each enumerator is the type of its initializing value: — If an initializer is specified for an enumerator, the initializing value has the same type as the expression and the constant-expression shall be an integral constant expression (5.19). — If no initializer is specified for the first enumerator, the initializing value has an unspecified integral type. — Otherwise the type of the initializing value is the same as the type of the initializing value of the preceding enumerator unless the incremented value is not representable in that type, in which case the type is an unspecified integral type sufficient to contain the incremented value. If no such type exists, the program is ill-formed.

In C, an enum constant is of type int. In C++, it's of the enumerated type.

enum en_e{
en_e_foo,
en_e_bar=UINT64_MAX,
};

In C, this is a constraint violation, requiring a diagnostic (if UINT64_MAX exceeds INT_MAX, which it very probably does). A C compiler may reject the program altogether, or it may print a warning and then generate an executable whose behavior is undefined. (It's not 100% clear that a program that violates a constraint necessarily has undefined behavior, but in this case the standard doesn't say what the behavior is, so that's still undefined behavior.)

gcc 6.2 doesn't warn about this. clang does. This is a bug in gcc; it incorrectly inhibits some diagnostic messages when macros from standard headers are used. Thanks to Grzegorz Szpetkowski for locating the bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71613

In C++, each enumeration type has an underlying type, which is some integer type (not necessarily int). This underlying type must be able to represent all the constant values. So in this case, both en_e_foo and en_e_bar are of type en_e, which must be at least 64 bits wide, even if int is narrower.

As others pointed, the code is ill-formed (in C), because of constraint violation.

There is GCC bug #71613 (reported June 2016), which states that some useful warnings are silenced with macros.

Useful warnings seem to be silenced when macros from system headers are used. For example, in the example below a warning would be useful for both enums but only one warning is shown. The same can probably happen for other warnings.

The current workaround may be to prepend the macro with unary + operator:

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

which yields compilation error on my machine with 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