30

Suppose I have (on a 32 bit machine)

enum foo { val1 = 0x7FFFFFFF, // originally '2^31 - 1' val2, val3 = 0xFFFFFFFF, // originally '2^32 - 1' val4, val5 }; 

what is the value of val2, val4 and val5? I know I could test it, but is the result standardized?

12
  • 1
    At best this is going to be implementation-defined, the size of an enum isn't capped. Commented Jun 11, 2013 at 13:45
  • 12
    Just for laughs, both expressions would compile, and produce very small numbers - 28 and 29. Commented Jun 11, 2013 at 13:49
  • 1
    @DyP No, it was a XOR operator before - OP used it to illustrate "two to the power". Commented Jun 11, 2013 at 14:01
  • 2
    @Bathsheba: C and C++ are different languages; which one do you want? Commented Jun 11, 2013 at 15:06
  • 2
    @Bathsheba: I saw that you tagged it as both. That's my point: the answers are different between them. Commented Jun 11, 2013 at 15:14

4 Answers 4

16

In C standard:

C11 (n1570), § 6.7.2.2 Enumeration specifiers

Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined, but shall be capable of representing the values of all the members of the enumeration.

If the underlying type used by the compiler is not capable to represent these values, the behavior is undefined.

C11 (n1570), § 4. Conformance

If a ‘‘shall’’ or ‘‘shall not’’ requirement that appears outside of a constraint or runtime-constraint is violated, the behavior is undefined.

Sign up to request clarification or add additional context in comments.

8 Comments

But isn't uint_least64_t required to exist? Isn't it an unsigned integer type?
@Dyp, yes, so the compiler can choose it. However, if you do 0xFFFFFFFFFFFFFFFFllu then it would be undefined behavior if the compiler doesn't support integers larger than 64 bits.
Also, unsigned long long is required to be able to store at least 2^64-1 ("to the power of", not "XOR"), therefore 0xFF FF FF FF shouldn't be a problem.
I might be wrong, but I think it's more relevant that C requires the type of the enumerators to be int (strictly), 6.7.2.2
@DyP do you have a quote? The quote above only demands an "integer type" wich might be long, uint_least64_t or others.
|
11

From the C++11 standard (§7.2,6, emphasis mine):

For an enumeration whose underlying type is not fixed, the underlying type is an integral type that can represent all the enumerator values defined in the enumeration. If no integral type can represent all the enumerator values, the enumeration is ill-formed. It is implementation-defined which integral type is used as the underlying type except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.

So the compiler will happily do The Right Thing if there is an integral type bigger than 32bit. If not, the enum is illformed. There will be no wrapping around.

The values will be:

enum foo { val1 = 0x7FFFFFFF, val2, // 0x80000000 = 2^31 val3 = 0xFFFFFFFF, val4, //0x0000000100000000 = 2^32 val5 //0x0000000100000001 = 2^32+1 }; 

The increasing numbers are well defined as well (§7.2,2):

[...] An enumerator-definition without an initializer gives the enumerator the value obtained by increasing the value of the previous enumerator by one.

5 Comments

"if there is an integral type bigger than 32bit" And there is, unsigned long long is required by C99 to be able to store at least 2^64 -1. There's uint_least64_t as well.
Again, I think it's more relevant what the type of the enumerator is, not the type of the enumeration; see [dcl.enum]/5. "If the underlying type is not fixed, the type of each enumerator is the type of its initializing value", but it'll grow according to subbullet 3 (in contrast to the requirements in C99/11)
I think the enumeration is a type and does not have a type. The type of the enumerators is foo in C++, other than in C. §7.2,1 of the C++ standard clearly states "An enumeration is a distinct type with named constants"
Yes, the enumeration is a type, but the same holds for C, 6.2.5 /16 "Each distinct enumeration constitutes a different enumerated type." Yet, I still think it's not relevant if the enumeration is a type and what underlying type it has, since the OP talks about enumerators.
You might want to add this "Following the closing brace of an enum-specifier, each enumerator has the type of its enumeration." and some of the following of [dcl.enum]/5
6

C99 / C11

Prelude:

5.2.4.2.1 requires int to be at least 16 bits wide; AFAIK there's no upper bound (long must be longer or equal, though, 6.2.5 /8).

6.5 /5:

If an exceptional condition occurs during the evaluation of an expression (that is, if the result is not mathematically defined or not in the range of representable values for its type), the behavior is undefined.


If your `int` is 32 bits wide (or less)

then the example in the OP is a violation of constraint 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.

Furthermore, the enumerators are defined as constant of type int, 6.7.2.2 /3:

The identifiers in an enumerator list are declared as constants that have type int and may appear wherever such are permitted.


Note, there's a difference between the type of the enumeration and the type of an enumerator / enumeration constant:

enum foo { val0 }; enum foo myVariable; // myVariable has the type of the enumeration uint_least8_t v = val0*'c'; // if val0 appears in any expression, it has type int 


It seems to me this allows narrowing, e.g. reducing the size of the enum type to 8 bits:

enum foo { val1 = 1, val2 = 5 }; enum foo myVariable = val1; // allowed to be 8-bit 

But it seems to disallow widening, e.g.

enum foo { val1 = INT_MAX+1 }; // constraint violation AND undefined behaviour // not sure about the following, we're already in UB-land enum foo myVariable = val1; // maximum value of an enumerator still is INT_MAX // therefore myVariable will have sizeof int 

Auto-increment of enumerators

Because of 6.7.2.2 /3,

[...] Each subsequent enumerator with no = defines its enumeration constant as the value of the constant expression obtained by adding 1 to the value of the previous enumeration constant. [...]

the example results in UB:

enum foo { val0 = INT_MAX, val1 // equivalent to `val1 = INT_MAX+1` }; 

1 Comment

int can be as narrow as 16 bits.
2

Here's the C++ answer: in 7.2/6, it states:

[...] the underlying type is an integral type that can represent all the enumerator values defined in the enumeration. If no integral type can represent all the enumerator values, the enumeration is ill-formed. It is implementation-defined which integral type is used as the underlying type except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.

So compared to C: no undefined behavior if the compiler can't find a type, and the compiler can't just use its 512-bit extended integer type for your two-value enum.

Which means that in your example, the underlying type will probably be some signed 64-bit type - most compilers always try the signed version of a type first.

4 Comments

val4 and val5 won't fit into an 32bit unsigned int, so the compiler has to use a type bigger than that.
Right, forgot about those.
@SebastianRedl: Are you sure about signedness ? I thought that gcc and Clang would preferably use the unsigned counterpart (unless there was a negative value).
Checking the code of Clang, it appears it prefers unsigned types for C and signed types for C++. I'd wager a guess that GCC does it the same way - compatibility is the #1 reason why Clang does such weird stuff.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.