Join the Stack Overflow Community
Stack Overflow is a community of 6.6 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

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?

share|improve this question
    
Which compilers? I don't know if it makes a difference, but it might. – Mark Ransom 12 hours ago
    
@MarkRansom It came up with gcc but clang behaves the same. – PSkocik 12 hours ago
    
Live example of C – Drew Dormann 12 hours ago

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.

share|improve this answer
3  
quick note: for UINT64_MAX not to exceed INT_MAX requires that int is at least 65 bits. – Ben Voigt 11 hours ago
5  
The really strange thing is that gcc (5.3.1) emits a warning with -Wpedantic and 18446744073709551615ULL but not with UINT64_MAX. – nwellnhof 11 hours ago
2  
@dascandy: No, int must be a signed type, so it would have to be at least 65 bits to be able to represnt UINT64_MAX (2**64-1). – Keith Thompson 10 hours ago
2  
1  
@KeithThompson, 6.7.2.2 says that "the identifiers in an enumerator list are declared as constants that have type int and may appear wherever such are permitted." My understanding is that the constants that a single C enum declares do not use the enum's type, so from there it's not a large stretch to make them different types (especially if it is implemented as an extension to the standard). – zneak 8 hours ago

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 it's 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.

share|improve this answer
9  
It is a compiler extension, but failure to generate a diagnostic is a non-conformance. – Ben Voigt 11 hours ago
    
@BenVoigt You're right. – HolyBlackCat 11 hours ago

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.

share|improve this answer

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
share|improve this answer

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 bug and it failed to produce the diagnostic message. (Bug is pointed in the answer by Grzegorz Szpetkowski

share|improve this answer
6  
"undefined behavior" is a runtime effect. sizeof is a compile-time operator. There's no UB here, and even if there were, it couldn't affect sizeof. – Ben Voigt 12 hours ago
2  
You should find the standard quote that enumerants that can't fit in an int are UB. I am highly skeptical of that statement and my vote will stay a solid -1 until this is cleared up. – zneak 12 hours ago
3  
@Sergey: The C standard actually does say "The expression that defines the value of an enumeration constant shall be an integer constant expression that has a value representable as an int." but violating this would be a constraint violation, diagnostic required, not UB. – Ben Voigt 12 hours ago
3  
@haccks: Yes? It's a constraint violation, and "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." – Ben Voigt 12 hours ago
2  
There's a difference between overflow and truncation. Overflow is when you have an arithmetic operation that produces a value too large for the expected result type, and signed overflow is UB. Truncation is when you have a value that was too big for the target type to begin with (like short s = 0xdeadbeef), and the behavior is implementation-defined. – zneak 11 hours ago

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.

share|improve this answer
3  
It's not "undefined" in C, it's "ill-formed" because a constraint is violated. The compiler MUST generate a diagnostic concerning the violation. – Ben Voigt 11 hours ago
    
@BenVoigt Thanks for teaching me about the difference. Fixed it in the answer (which I made because I missed a quotation from the C++ standard in the other answers). – PSkocik 10 hours ago

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.