Keith Thompson wrote: [snippage]
C99 6.7.2.2 says:
The identifiers in an enumerator list are declared as constants
that have type int and may appear wherever such are permitted.
...
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.
So sub_obj was chosen by the compiler to be an unsigned type when I
don't have a -10 or -1 initializer. But it chose the type to be int
when I did not have any initializer ...
(I am not entirely sure what you mean by "have ... initializer(s)",
so I will recap.)
In any case, "enum" types are declared via the following syntax:
enum tag { enumerator-constant-list }
where the "tag" is optional and the constant-list gives identifiers
and optional values. While the syntax for an identifier with a
value is, e.g.:
enum x { Y = 42 };
this is not technically an "initializer": this creates a type named
"enum x" and a constant named Y. The type -- the thing named "enum
x" -- is "compatible with" (i.e., at machine level really happens
to be the same as) some integral type, such as char, or unsigned
long, or whatever. The constant -- the thing named "Y" -- has type
int. C99 adds a requirement that was missing in C90: in C90, one
could write, e.g.:
enum foo { BIG = 32100 };
and yet the compiler might choose "enum foo" to be compatible
with "unsigned char", even though UCHAR_MAX is 255. Then if you
did:
enum foo var = BIG;
this would set "var" to (32100 % 256) or 100. In C99, this cannot
happen; "var" has to be big enough to hold the value 32100.
The C99 rule *does* mean that if you write, e.g.:
enum plusminus { MINUS = -1, ZERO, PLUS };
no C99 compiler can choose an unsigned type for "enum plusminus",
because one of the values it has to hold is -1, which is negative.
MINUS is just a name for -1, ZERO just a name for 0, and PLUS just
a name for 1, however; and all three of these names produce values
of type "int", even if "enum plusminus" is really "signed char" or
"signed short" or something along those lines.
If you leave out the "= value" part, each enumeration constant
(Y, BIG, MINUS, and so on in the examples above) takes on one
more than the previous value, or 0 for the first value. So if
you do something like:
enum e { FOO_STORE, FOO_1, FOO_2, FOO_3, FOO_4, FOO_MAX };
then all the constants are nonnegative and "enum e" can be an
unsigned type.
Proceeding onward, given the above "enum e" and a variable
declared as:
enum e sub_obj;
This seems to work (no infinite loop):
for (sub_obj = FOO_MAX-1; (int) sub_obj >= FOO_STORE; --sub_obj) {
This will generally work but is not guaranteed. Either sub_obj
has some signed integral type, in which the (int) cast is redundant,
or sub_obj has some unsigned integral type.
This second case is where the problem occurs. Suppose sub_obj is
actually "unsigned int" underneath. In this case, out-of-range
positive values almost always become negative values. (Technically
the result is implementation-defined, but in practice it is not a
problem.) But suppose instead sub_obj is actually "unsigned char"
underneath. Then if sub_obj is currently FOO_STORE, --sub_obj
means "set unsigned char sub_obj to (0-1) mod (UCHAR_MAX+1)",
which on most machines will set it to 255. On the next trip
through the loop, the test will compare (int)255 >= 0, which is
of course true, so the loop will continue to run.
I'm thinking the guaranteed way to avoid the infinite loop is
for (sub_obj = FOO_MAX-1; (int) sub_obj >= (int) FOO_STORE; --sub_obj)
This is no different from the first loop. FOO_STORE is an
enumeration constant, not an object with some enumerated type,
and enumeration *constants* have type "int". The extra cast in
the test says "convert this int to int", which of course has
no effect on it.
The absolute guaranteed method is a "test at bottom" style loop,
for which C lacks a convenient construct. If it had one, it might
read:
for_until_test_at_bottom (sub_obj = FOO_MAX - 1;
sub_obj == FOO_STORE; --sub_obj) {
...
}
which in C, in the general case, has to be written out as:
for (sub_obj = FOO_MAX - 1;; --sub_obj) {
...
label:
if (sub_obj == FOO_STORE)
break;
}
with "continue"s replaced with "goto label" as needed. Or one can
use a do ... while loop:
sub_obj = FOO_MAX - 1;
do {
...
} while (sub_obj-- != FOO_STORE);
which avoids the need for a goto, but (a) separates out the loop
initialization, and (b) requires that the test and "increment" --
decrement, in this case -- at the bottom of the loop be combined.
(In this case, it all works pretty well.)