Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set the value of an enumeration constant outside the range of int?

The C99 standard requires that the expression used to define the value of an enumeration constant has a value representable as an int.

In section 6.7.2.2 paragraph 2 of the C99 standard:

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

However, enumerated types can be defined by the implementation to be compatible with any integer type, including those with a range of values outside of int.

In section 6.7.2.2 paragraph 2 of the C99 standard:

Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type.

This means that while you cannot explicitly set the value of an enumeration constant outside the range of an int, the value of an enumeration constant can be outside the range of an int if the implementation defines the enumeration type to be compatible with an integer type with a range outside of int.


Now I know one way to get a specific value outside the range of int set for an enumeration constant: dummy enumerators.

enum hack{
    DUMMY0 = INT_MAX,
    DUMMY1,
    /* supply as many more dummy enumerators as needed */
    ...
    /* declare desired enumerator */
    FOOBAR
};

This works thanks to section 6.7.2.2 paragraph 3 of the C99 standard:

An enumerator with = defines its enumeration constant as the value of the constant expression.
...
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.

Unfortunately, this only works for positive values greater than INT_MAX, since the value of each subsequent enumerator is only ever incremented. Another caveat is the need to create possibly many dummy enumerators just to acquire the specific enumerator desired.


This leads to the following questions:

  1. Is there a way to set the value of an enumeration constant to a negative value outside the range of int?
  2. Is there a better way to set a positive value outside the range of int to an enumeration constant?
  3. Regarding my dummy enumerator hack, does the C99 standard set a limit on the number of enumerators which may be declared in a single enum?
like image 522
Vilhelm Gray Avatar asked Aug 06 '13 21:08

Vilhelm Gray


People also ask

How do you create an enumeration constant?

The syntax for enumerated constants is to write the keyword enum, followed by the type name, an open brace, each of the legal values separated by a comma, and finally, a closing brace and a semicolon. Here's an example: enum COLOR { RED, BLUE, GREEN, WHITE, BLACK };

What is the type of the enumeration constants?

An enumeration is a data type that consists of a set of named values that represent integral constants, known as enumeration constants. An enumeration is also referred to as an enumerated type because you must list (enumerate) each of the values in creating a name for each of them.

Can an enumeration constant appear more than one type of defination?

So the standard indicates that there is only one definition of an enumeration constant.


2 Answers

How to set the value of an enumeration constant outside the range of int?

You don't.

The C99 standard requires that the expression used to define the value of an enumeration constant has a value representable as an int.

Yes, and the C11 standard didn't change any of this.

However, enumerated types can be defined by the implementation to be compatible with any integer type, including those with a range of values outside of int.

Also correct.

This means that while you cannot explicitly set the value of an enumeration constant outside the range of an int, the value of an enumeration constant can be outside the range of an int if the implementation defines the enumeration type to be compatible with an integer type with a range outside of int.

That's incorrect, but I think you've found a weakness in the wording in the standard. (Update: I don't think it's really a weakness; see below). You quoted 6.7.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.

which seems to apply only when the value is defined by an explicit expression, not to a case like this:

enum too_big {
    big = INT_MAX,
    even_bigger
};

But this doesn't actually work, since even_bigger is declared as a constant of type int, which clearly cannot have the value INT_MAX + 1.

I strongly suspect that the intent is that the above declaration is illegal (a constraint violation); probably 6.7.2.2 should be reworded to make that clearer. (Update: I now think it's clear enough; see below.)

The authors of gcc seem to agree with me:

$ cat c.c
#include <limits.h>
enum huge {
    big = INT_MAX,
    even_bigger
};
$ gcc -c c.c
c.c:4:5: error: overflow in enumeration values

so even if your interpretation is correct, you're not likely to be able to write and use code that depends on it.

A workaround is to use integers (enumeration types are more or less thinly disguised integers anyway). A const integer object isn't a constant expression, unfortunately, so you might have to resort to using the preprocessor:

typedef long long huge_t;
#define big ((huge_t)INT_MAX)
#define even_bigger (big + 1)

This assumes that long long is wider than int, which is likely but not guaranteed (int and long long could be the same size if int is at least 64 bits).

The answer to your questions 1 and 2 is no; you can't define an enumeration constant, either negative or positive, outside the range of int.

As for your question 3, section 5.2.4.1 of the C11 standard says (roughly) that a compiler must support at least 1023 enumeration constants in a single enumeration. Most compilers don't actually impose a fixed limit, but in any case all of the constants must have values within the range INT_MIN .. INT_MAX, so that doesn't do you much good. (Multiple enumeration constants in the same type can have the same value.)

(The translation limit requirement is actually more complicated than that. A compiler must support at least one program that contains at least one instance of all of an enumerated list of limits. That's a fairly useless requirement as stated. The intent is that the easiest way to meet the requirement given by the Standard is to avoid imposing any fixed limits.)

UPDATE :

I raised this issue on the comp.std.c Usenet newsgroup. Tim Rentsch raised a good point in that discussion, and I now think that this:

enum too_big {
    big = INT_MAX,
    even_bigger
};

is a constraint violation, requiring a compiler diagnostic.

My concern was that the wording that forbids an explicit value outside the range of int:

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

does not apply, since there is no (explicit) expression involved. But 6.7.7.2p3 says:

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.

(emphasis added). So there is an expression whose value must be representable as an int; it just doesn't appear in the source. I'm not 100% comfortable with that, but I'd say the intent is sufficiently clear.

Here's the discussion on comp.std.c.

like image 128
Keith Thompson Avatar answered Oct 18 '22 17:10

Keith Thompson


Maybe I cannot say anything that Keith Thompson has not told you, yet.
I'll do my try, anyway.

1. Values in the range of int

In the document of ISO C99, I can see in section 6.7.2.2, paragraphs 2 and 3, the following statements:

(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.

If you write enum T { X = (expr) } VAR, then expr is an integer number in the range of an int, which includes, at least, the range -32767 .. +32767, as you can read in 5.2.4.2.1.
Of course, the paragraph (2) does not imposes any restriction on the type of the identifier X.

2. Enumerator identifiers have type int

In paragraph 3 we can read this line:

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

This is a restriction on the type of the identifier. Now X has type int.

3. Discussion about the values

Also, the standard says:

(3b) An enumerator with = defines its enumeration constant as the value of the constant expression.

But this sentence, now, is restricted by (2) and (3a).
So, my interpretation of the standard C99 is as follows: If you write

 enum T { X = INT_MAX, BIGG}  

then BIGG has type int (according to (3a)).
As Keith Tompson pointed out, the value (="enumerator constant?") of BIGG is not coming from an expression, representing an out-of-range value (for int). Its value (in mathematical sense) is X+1, because the next rule is applied:

(3c) 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.

There is not any rule in the standard (about integer arithmetic) that defines the behaviour of the compiler in this case. So, it would fall in the implementation defined class...
However, in the case that the compiler accepts this out-of-range value, I believe that the (mathematical) X+1 will be converted to a value in the int range.

But, the intended behaviour in (3b) seems to be that the C expression (X+1) == BIGG is always true. If I am right, then I am agreeing with Keith, and the compiler has to reject the declaration with an Out of range error.

4. The integer type of enumerated types

We can read this even more:

(4a) Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type.

The declaration enum T defines a new integer type.
This type is not neccesarilly related to the type of expression expr, neither to the type of enumerator X.
It is just another type for another thing: the integer type associated to the enum type T we are defining.
(This type will be the one assigned to the variable VAR_T).

The implementation can decide what integer type is the more appropiated.
If the expressions (like expr) have very small values, as almost always happens, then the compiler may decide that T is of type char, for example.
If a long long is desired for some reason, then the type of T will be long long, and so on.

However, this does not change the restrictions to int that the expression expr and the enumerator X have to follow. They are int.
The only rule that relates the types of expr and T is:

(4b) The choice of type is implementation-defined, but shall be capable of representing the values of all the members of the enumeration.

Thus, if you have enum T { X = 0, Y = 5, Z = 9 }, then the type of T is able to be a char.
(The situation is analogous to the case that a character constant, like 'c', which always has type int, is passed to a char variable: char c = 'c';: although 'c' is an int, its value fits in the range of a char).

On the other hand, if you have enum T { X = 20202, Y = -3 }, the compiler cannot choose char for T. It seems that int is the perfect type for T (as for any enum type), but the compiler can choose any other integer type for T whose range contains the values 20202 and -3.

If none value is negative, then the compiler could choose an unsigned integer type.

5. Summary

In summary, we can say that:

  1. There are 4 types involved in an enum declaration: for the expressions (expr), the values (or enumeration constants, coming from expressions or just implicit), the enumerators (X), and the enum type (T).
  2. The type of expressions (as expr) is always int. The type of the values seems hardly intended to be in the range of an int (INT_MAX+1 seems to be not allowed). The type of the enumerators is int (as X). And the enum types (as T) are chosen by the implementation, among the all possible integer types allowed, just fitting the values inside the declaration.
  3. In expressions, we have that:

    #define NN 5
    enum T { X = 0. Y = 3, Z = (NN*3) } evar;

The expression (NN * 3) + 1 is an int.
The expression (Z + 1) is an int.
If the compiler defines T as a char,
then the expression ((evar = Z), ++evar) is a char.

like image 35
pablo1977 Avatar answered Oct 18 '22 16:10

pablo1977