enum type int or unsigned int?

K

kar1107

Hi all,

Can the compiler chose the type of an enum to be signed or unsigned
int? I thought it must be int; looks like it changes based on the
assigned values. Below if I don't initialize FOO_STORE to be, say -10,
I get a warning about unsigned comparison and I'm seeing an infinite
loop.

If I do initialize FOO_STORE = -10, I don't see any warnings. No
infinite loop.

Isn't the type of FOO_xyz required to be an 'int'?
Can the compiler choose its type to be unsigned int?

--------
#include <stdio.h>

typedef enum {
FOO_STORE , /* if = -10 is added, no warnings. works fine */
FOO_HASHTABLE,
FOO_STATE,
FOO_CONFIG,
FOO_MAX
} foo_subobj_type_en;


int main(void) {
foo_subobj_type_en sub_obj;

for (sub_obj = FOO_MAX-1; sub_obj >= FOO_STORE; --sub_obj) {
printf("%d\n", sub_obj);
}
return 0;
}
gcc --version
gcc (GCC) 3.2.3 20030502 (Red Hat Linux 3.2.3-52)
gcc -Wall -W -ansi -pedantic enum.c
enum.c: In function `main':
enum.c:15: warning: comparison of unsigned expression >= 0 is always
true
Thanks,
Karthik
 
D

danielhe99

foo_subobj_type_en sub_obj;

So the value range of sub_obj is
FOO_STORE ,
FOO_HASHTABLE,
FOO_STATE,
FOO_CONFIG,
FOO_MAX
for (sub_obj = FOO_MAX-1; sub_obj >= FOO_STORE; --sub_obj)
sub_obj will be in the range of FOO_STORE ~ FOO_MAX.

sub_obj >= FOO_STORE always is TRUE. That is why you get the warning.

If you cast enum type into "int", FOO_STORE~FOO_MAX will be treated as
"int".
In this case, you can assign FOO_STORE = integer value, all FOO_*
become CONST INT.
 
M

Micah Cowan

Hi all,

Can the compiler chose the type of an enum to be signed or unsigned
int? I thought it must be int; looks like it changes based on the
assigned values. Below if I don't initialize FOO_STORE to be, say -10,
I get a warning about unsigned comparison and I'm seeing an infinite
loop.

If I do initialize FOO_STORE = -10, I don't see any warnings. No
infinite loop.

Isn't the type of FOO_xyz required to be an 'int'?
Can the compiler choose its type to be unsigned int?

--------
#include <stdio.h>

typedef enum {
FOO_STORE , /* if = -10 is added, no warnings. works fine */
FOO_HASHTABLE,
FOO_STATE,
FOO_CONFIG,
FOO_MAX
} foo_subobj_type_en;


int main(void) {
foo_subobj_type_en sub_obj;

for (sub_obj = FOO_MAX-1; sub_obj >= FOO_STORE; --sub_obj) {
printf("%d\n", sub_obj);
}
return 0;
}

gcc (GCC) 3.2.3 20030502 (Red Hat Linux 3.2.3-52)

enum.c: In function `main':
enum.c:15: warning: comparison of unsigned expression >= 0 is always
true

This behavior would be allowed in C99... but since you're invoking GCC
in what is supposed to be C89-mode...

My draft copy of C89 says very clearly that enumerated types have a
type compatible with int. An object of that type would necessarily be
signed, unless I'm missing something.

C99 allows the implementation to choose a type that is compatible with
any of char, any signed integer type, or any unsigned integer
type. The constants /themselves/, must have type int.

Looks to me like GCC's broken in this regard.
 
K

Keith Thompson

Micah Cowan said:
My draft copy of C89 says very clearly that enumerated types have a
type compatible with int. An object of that type would necessarily be
signed, unless I'm missing something.

C99 allows the implementation to choose a type that is compatible with
any of char, any signed integer type, or any unsigned integer
type. The constants /themselves/, must have type int.

Did that change between your C89 draft and the actual standard? (I
wonder if you're confusing the type of the enum literals with the
compatible type of the enum type itself.)

According to my copy of the ISO C90 standard, section 6.5.2.2:

The identitiers 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 an integer type; the
choice of type is implementation-defined.

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.

which is nearly the same.
 
K

kar1107

Keith said:
Did that change between your C89 draft and the actual standard? (I
wonder if you're confusing the type of the enum literals with the
compatible type of the enum type itself.)

According to my copy of the ISO C90 standard, section 6.5.2.2:

The identitiers 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 an integer type; the
choice of type is implementation-defined.

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 (in which case I didn't see the
warning or an infinite loop).

I could avoid the infinite loop by casting subobj to be int. But I'm
not sure if this is guaranteed to work always.

This seems to work (no infinite loop):

for (sub_obj = FOO_MAX-1; (int) sub_obj >= FOO_STORE; --sub_obj) {

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)
{

In the first case, isn't it still possible the left int is converted to
an unsigned (ie -1 back to the 4 billon plus number on a 32bit system)
and the comparison is made? ie if sizeof(int) == sizeof(unsigned int),
the int is changed to unsigned. May be this rule doesn't apply in case
of explicit int cast?

Karthik
 
M

Micah Cowan

Keith Thompson said:
Did that change between your C89 draft and the actual standard? (I
wonder if you're confusing the type of the enum literals with the
compatible type of the enum type itself.)

No; I saw that that distinction had been made in C99, but couldn't
find it in C90.

....For some reason, as I read it now, I /do/ find the text to be
similar to what C99 says. I'm really at a loss to explain how I read
it otherwise... I specifically looked for the text "enumerated type".
Unless I somehow read "integer type" to mean "int"... very weird.

Anyway, reading it again, I find the text to be as you described for
ISO C90...
According to my copy of the ISO C90 standard, section 6.5.2.2:

The identitiers 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 an integer type; the
choice of type is implementation-defined.

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.

which is nearly the same.

Right. In fact, isn't it semantically the same? I guess they were
simply making it clearer that you can use unsigned types. The explicit
mention of char seems pretty redundant to me, though, since it's
obviously going to be either a signed or unsigned type.
 
C

Chris Torek

Keith Thompson wrote:
[snippage]
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.)
 
D

Dave Thompson

Right. In fact, isn't it semantically the same? I guess they were
simply making it clearer that you can use unsigned types. The explicit
mention of char seems pretty redundant to me, though, since it's
obviously going to be either a signed or unsigned type.

Not exactly.

'plain' char must 'act like' (have the same representation, and
behavior) as one of signed char or unsigned char at the
implementation's option, sometimes selectable by the user, but is
still a distinct type and is not included in either of the classes
'signed integer type' or 'unsigned integer type'.

'integer types' (which was actually spelled 'integral types' in C90)
includes plain char, signed integer types, unsigned integer types, and
enumeration types. Using it in C90 6.5.2.2 is formally recursive: an
enumeration type can be compatible with an enumeration type, which
presumably is compatible with an enumeration type, etc. Obviously to
any sensible person, this was not intended or useful, but the new
wording specifically prevents it.

- David.Thompson1 at worldnet.att.net
 
K

kar1107

Chris said:
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.)

Thanks Chris for the explanation. I did feel the double (int) cast
looked a bit awkward. But again there are some vary valid uses for
casts. I think in my system, gcc chose unsigned int (instead of
unsigned char) and hence I didn't see the infinite loop. I better
change this code to one of the way's you've suggested.

I think I can do the plain 0 to FOO_MAX-1 for some cases (that is no
need for the "reverse" iteration on the enum's). BTW if I go from 0 to
FOO_MAX, I still run into the risk of infinite loop?

for (sub_obj = FOO_STORE; sub_obj <= FOO_MAX; ++sub_obj)

Note: I added a <= (not <). If FOO_MAX=255 and compiler chose unsigned
char for sub_obj, I run into the risk of infinite loop.

Thanks,
Karthik
 
C

CBFalconer

Chris said:
.... snip ...

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) {
...
}

I strongly disagree. That is the purpose of the do loop:

do {
/* whatever */
} while (appropriate);

--
"If you want to post a followup via groups.google.com, don't use
the broken "Reply" link at the bottom of the article. Click on
"show options" at the top of the article, then click on the
"Reply" at the bottom of the article headers." - Keith Thompson
More details at: <http://cfaj.freeshell.org/google/>
Also see <http://www.safalra.com/special/googlegroupsreply/>
 
M

Michael Mair

CBFalconer said:
Chris Torek wrote:

... snip ...


I strongly disagree. That is the purpose of the do loop:

do {
/* whatever */
} while (appropriate);

I am not sure whether you understood Chris Torek's point;
sub_obj = InitValue + 1;
do {
--sub_obj;
....
} while (!(sub_obj == FOO_STORE));
is not exactly convenient; if the starting point (InitValue)
were FOO_MAX instead of FOO_MAX - 1, and FOO_MAX coincided with
sub_obj's type maximum, then the addition of one would not be
possible and you'd need
sub_obj = InitValue;
first = 1;
do {
if (!first)
--sub_obj;
else
first = 0;
....
} while (!(sub_obj == FOO_STORE));
which is not exactly convenient. Of course, you still can express
everything with every kind of loop (plus some stuff). In this
case, tricks with the for loop continuation condition may lead to
cleaner code than the do--while variant. ("first || !(sub_obj ==
FOO_STORE)")


Cheers
Michael
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top