cenumsenumerationc99language-lawyer

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?

Solution

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

    You don't. (For now, but see UPDATE 2 below if you're using a C23 or later compiler.)

    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.

    UPDATE 2:

    The 2023 edition of the ISO C standard (not yet published as I write this, but the latest draft is N3054) allows an enum-type-specifier to specify the underlying type of an enumeration type.

    gcc 13.1.0 has partial support for C23 with the -std=c2x option.

    #include <stdio.h>
    #include <limits.h>
    int main(void) {
        enum small { zero, one };
        enum big : long long { BIG = UINT_MAX + 1LL, BIGGER };
        printf("%d %d %lld %lld\n", zero, one, BIG, BIGGER);
    }
    

    The output with gcc 13.1.0 on my system is:

    0 1 4294967296 4294967297