gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

C/C++ > enum question

#162237 - gauauu - Thu Aug 28, 2008 4:42 pm

I recently switched over from using my own bool (typedef'd from an int) to the bool defined by libnds (enum'd as true/fase). Some oddities came up in my code that made me wonder what happens under the covers with enums in C.

Say I have a function like:
Code:

bool checkForMagicBits(uint32 value, uint32 bitflag)
{
    return (value & bitflag);
}


When I declared bool to just be an int, this would work great, I could do:
Code:

if (checkForMagicBits(512, 512)) .... do something


But now, if instead, I use the bool from libnds:

Code:
typedef enum { false, true } bool;


Then it really gets interesting. Passing in 32, 32 works the same way as before (returns a non-zero, thus true, value), but passing in 512, 512 actually seems to return a 0 (which C thinks is false).

Obviously this has to do with assigning "wrong" int values to an enum, but does anybody know what's really happening under the covers here?

It's easy enough to fix by doing something like
Code:

bool checkForMagicBits(uint32 value, uint32 bitflag)
{
    return ((value & bitflag) != 0);
}

But I'm rather curious about what's really happening.

#162238 - Miked0801 - Thu Aug 28, 2008 4:48 pm

My guess is the 'bool' is being defined as a u8. 512 = 0 when assigned to a u8. If you are returning a bool, you should return a bool

Code:

return (value & bitflag) != 0;

#162239 - sgeos - Thu Aug 28, 2008 4:49 pm

IIRC, you can assign values to enumarated names like this:
Code:
typedef enum { false=0, true=1 } bool;

I could not figure out how GCC automatically assigned enums,
so I explicitly set the values last time I worked with them.

I pulled this of the net:
Code:
enum DAY            /* Defines an enumeration type    */
{
    saturday,       /* Names day and declares a       */
    sunday = 0,     /* variable named workday with    */
    monday,         /* that type                      */
    tuesday,
    wednesday,      /* wednesday is associated with 3 */
    thursday,
    friday
} workday;

Does the standard guarantee wednesday to be 3?
I recall actual enum values not working as expected.

-Brendan

#162247 - gauauu - Thu Aug 28, 2008 5:15 pm

Miked0801 wrote:
My guess is the 'bool' is being defined as a u8. 512 = 0 when assigned to a u8. If you are returning a bool, you should return a bool


My guess as well, which is why my solution matched yours ;-)

sgeos wrote:
IIRC, you can assign values to enumarated names


Yeah, that's what I've found to be the easiest way of dealing with things at other times.

#162253 - keldon - Thu Aug 28, 2008 7:01 pm

Bearing in mind that bool is an enumeration; consider the following:
Code:
enum Day
{
   MONDAY,
   TUESDAY,
   WEDNESDAY,
   THURSDAY,
   FRIDAY,
   SATURDAY,
   SUNDAY
};

void function (void)
{
   Day day = 0;
   Day another_day = (5 & 4) != 0;
}


That is effectively what you are asking your code to do. In the context of days, it is obvious why the compiler would not like this without - at least - an explicit cast. Of course, you will want to be sure that you are casting a value that is one of your enumerations, especially since anything non-zero is treated as false in C/C++.

Of course, for the type safe nut, there is always (not completely tested, but compiles) ...
Code:
class Bool
{
   public:
      Bool (int i = 0)                  { value = char (i ? 1 : 0); }
      
      Bool (unsigned int i)               { value = char (i ? 1 : 0); }
      
      Bool& operator= (const Bool& b)         { value = b.value; return *this; }
      
      operator int (void) const            { return int (value ? 1 : 0); };
      
      
      Bool operator== (const Bool &b) const   { return value == b.value; };
      Bool operator!= (const Bool &b) const   { return value != b.value; };
      Bool operator! (void) const            { return Bool ( value ? 0 : 1 ); };
      Bool operator& (const Bool &b) const   { return Bool (value & b.value); };
      Bool operator| (const Bool &b) const   { return Bool (value | b.value); };
      
   private:
      char value;               //!< Value must always be 1 or 0.
}; // Bool

Bool& false_bool (void);
Bool& true_bool (void);
#define True (true_bool())
#define False (false_bool())

Bool& true_bool (void)
{
   static Bool bool_(1);
   return bool_;
}

Bool& false_bool (void)
{
   static Bool bool_ (0);
   return bool_;
}

#162269 - sajiimori - Fri Aug 29, 2008 2:06 am

Some bizzarre posts in this thread. The only issue was that GCC chose to represent your enum as a byte -- nothing to do with initializers or casts.

#162271 - elwing - Fri Aug 29, 2008 6:07 am

sajiimori wrote:
Some bizzarre posts in this thread. The only issue was that GCC chose to represent your enum as a byte -- nothing to do with initializers or casts.


that is really strange infact, aren't enum suposed to use machine base data type (int?)

#162272 - sgeos - Fri Aug 29, 2008 6:52 am

elwing wrote:
sajiimori wrote:
Some bizzarre posts in this thread. The only issue was that GCC chose to represent your enum as a byte -- nothing to do with initializers or casts.

that is really strange infact, aren't enum suposed to use machine base data type (int?)

I expected it used int too. I can see the logic behind behind using the smallest type that will be able to enumerate all of your options. Is this choice left up to the implementation, or does the standard have something to say about it?

For what it is worth, I compiled and ran an enum sizeof test:
Code:
#include <stdio.h>

#define PRINT_SIZEOF(t) printf("sizeof(%s)\t= %d;\n",#t,sizeof(t));

typedef enum {TRUE0              , FALSE0    } bool0;
typedef enum {TRUE1 = 0x000000001, FALSE1 = 0} bool1;
typedef enum {TRUE2 = 0x000000100, FALSE2 = 0} bool2;
typedef enum {TRUE3 = 0x000010000, FALSE3 = 0} bool3;
typedef enum {TRUE4 = 0x100000000, FALSE4 = 0} bool4;

int main(void)
{
        PRINT_SIZEOF(bool0);
        PRINT_SIZEOF(bool1);
        PRINT_SIZEOF(bool2);
        PRINT_SIZEOF(bool3);
        PRINT_SIZEOF(bool4);
        printf("---\n");
        PRINT_SIZEOF(TRUE0);
        PRINT_SIZEOF(TRUE1);
        PRINT_SIZEOF(TRUE2);
        PRINT_SIZEOF(TRUE3);
        PRINT_SIZEOF(TRUE4);
        printf("---\n");
        PRINT_SIZEOF(char);
        PRINT_SIZEOF(int);
        PRINT_SIZEOF(short);
        PRINT_SIZEOF(long);
        PRINT_SIZEOF(float);
        PRINT_SIZEOF(double);
        PRINT_SIZEOF(long long);
        PRINT_SIZEOF(long double);
        return 0;
}


It is interesting to note the warning I got when I compiled this:
Code:
$ cc     sizetest.c   -o sizetest
sizetest.c:9: warning: integer constant is too large for "long" type


It is also interesting to note that the compiler will use a size that can enumerate your values:
Code:
$ ./sizetest.exe
sizeof(bool0)   = 4;
sizeof(bool1)   = 4;
sizeof(bool2)   = 4;
sizeof(bool3)   = 4;
sizeof(bool4)   = 8;
---
sizeof(TRUE0)   = 4;
sizeof(TRUE1)   = 4;
sizeof(TRUE2)   = 4;
sizeof(TRUE3)   = 4;
sizeof(TRUE4)   = 8;
---
sizeof(char)    = 1;
sizeof(int)     = 4;
sizeof(short)   = 2;
sizeof(long)    = 4;
sizeof(float)   = 4;
sizeof(double)  = 8;
sizeof(long long)       = 8;
sizeof(long double)     = 12;
Also interesting, is that the standard size for enum is the size of int... or, more precisely long in this case, although they are the same size.

-Brendan

#162277 - keldon - Fri Aug 29, 2008 8:48 am

sajiimori wrote:
Some bizzarre posts in this thread. The only issue was that GCC chose to represent your enum as a byte -- nothing to do with initializers or casts.

Oops, wasn't thinking about GCC. I have a compiler I use very often that will moan at that, forcing you to write return bool((value & bitflag) != 0).

Edit: Visual C++ is one of them; which moans about converting from bool to Bool (after I renamed the enum), but also when I converted it to an int and tried to return it.

elwing wrote:
sajiimori wrote:
Some bizzarre posts in this thread. The only issue was that GCC chose to represent your enum as a byte -- nothing to do with initializers or casts.


that is really strange infact, aren't enum suposed to use machine base data type (int?)


C++ Standard 7.2.5 wrote:
The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration. It is implementation-defined which integral type is used as the underlying type for an enumeration 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. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0. The value of sizeof() applied to an enumeration type, an object of enumeration type, or an enumerator, is the value of sizeof() applied to the underlying type.


Last edited by keldon on Fri Aug 29, 2008 11:23 am; edited 1 time in total

#162278 - elwing - Fri Aug 29, 2008 9:20 am

keldon wrote:
sajiimori wrote:
Some bizzarre posts in this thread. The only issue was that GCC chose to represent your enum as a byte -- nothing to do with initializers or casts.

Oops, wasn't thinking about GCC. I have a compiler I use very often that will moan at that, forcing you to write return bool((value & bitflag) != 0).

elwing wrote:
sajiimori wrote:
Some bizzarre posts in this thread. The only issue was that GCC chose to represent your enum as a byte -- nothing to do with initializers or casts.


that is really strange infact, aren't enum suposed to use machine base data type (int?)


C++ Standard 7.2.5 wrote:
The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration. It is implementation-defined which integral type is used as the underlying type for an enumeration 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. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0. The value of sizeof() applied to an enumeration type, an object of enumeration type, or an enumerator, is the value of sizeof() applied to the underlying type.


okay, so that mean that the implementation of enum use an underlying type of 8bit int and that is a correct implementation, the question that I have now is: can you still use enum if you want to program for a ds/gba knowing that it will reduce performance without any needs?

#162284 - sgeos - Fri Aug 29, 2008 4:23 pm

elwing wrote:
okay, so that mean that the implementation of enum use an underlying type of 8bit int and that is a correct implementation,

In C++, yes. I'm not sure what the C standard says about the issue.

elwing wrote:
the question that I have now is: can you still use enum if you want to program for a ds/gba knowing that it will reduce performance without any needs?

You ought to be able to bump the size up by doing something like either one of these:
Code:
typedef enum { false=0, true=0x10000000 } bool;
typedef enum { false=0, true=1, bool_size_dummy_value=0x10000000 } bool;

That should take care of performance issues.

-Brendan

#162288 - sajiimori - Fri Aug 29, 2008 6:22 pm

I think there's a GCC flag to force it to use int for all enums. It's probably a good idea to enable that flag. (Is it something like -fno-short-enums?)

#162301 - Miked0801 - Sat Aug 30, 2008 6:49 pm

Or lame out and
Code:

typedef BOOL int;

#162305 - gauauu - Sat Aug 30, 2008 8:35 pm

Miked0801 wrote:
Or lame out and
Code:

typedef BOOL int;


Really, though, is there any reason that it would be less preferable to do this?

#162306 - elwing - Sat Aug 30, 2008 9:20 pm

gauauu wrote:
Miked0801 wrote:
Or lame out and
Code:

typedef BOOL int;


Really, though, is there any reason that it would be less preferable to do this?


not sure, but using an enum typedef instead of your bool typedef doesn't allow more type checks at compile time? at run time it is pretty equal (as long as the typedef enum compile as an int...)

#162325 - Miked0801 - Sun Aug 31, 2008 5:29 pm

The typedef takes away some of the compiler's ability to complain about when you use numeric expresions directly as bools. The compiler usually complains for a good reason - as the beginning of this thread showed :)

#162330 - gauauu - Sun Aug 31, 2008 8:08 pm

Except that using the enum, the compiler doesn't complain either.

This code:
Code:

typedef enum
{
    true,
    false
}bool;

bool check(int x)
{
    return 512 & x;
}


and

Code:

enum bool
{
    true,
    false
};

enum bool check(int x)
{
    return 47;
}


Compiled with no warnings in GCC 4.2.3 using -Wall or -Wextra

#162338 - sajiimori - Sun Aug 31, 2008 10:19 pm

In C, integer types will implicitly convert to enum types. C++ requires a cast.

Then again, bool is an integer type in C++. =)

#162468 - sniper - Wed Sep 03, 2008 6:10 pm

Maybe it helps ... this I'm using since years using C.
Also works well with static code checker.

Code:
typedef enum
{
    false = 0
  , true = 1
} bool_t;

#define BOOL_CAST(a) ((a) ? true : false)


Code:

bool_t check(int x)
{
    return BOOL_CAST(512 & x);
}

#162469 - tepples - Wed Sep 03, 2008 7:28 pm

Or just #define BOOL_CAST(a) (!!(a))
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#162472 - gauauu - Wed Sep 03, 2008 7:46 pm

Well, the problem isn't WHAT to do about it, which I answered in the original post. ;-)

The question was, what is really going on, according to the spec. The answer so far seems to be that the compiler can pick any size it wants to represent the enum, and was probably picking a u8. And the C compiler doesn't do any type checking against the enums to make sure you assign the right thing.

Feel free to keep discussing, but it seems to me that the question has been pretty well answered.