Monday, August 27, 2012

Comparing int with Enum

Problem: this may not be an interview question but it is quite interesting. Let's first look at some examples.  If we have following code that compare int with enum, what will happen (be printed out)? We will test these examples using gcc.

*Updated the post inspired by Nemo's great comments.

enum MY_ENUM{
  MY_OK = 0,
  MY_NOT_OK
};

void foo()
{
   int i = -1;
   enum My_ENUM my_enum = MY_OK;

   if( i < my_enum) printf("I am OK!\n");
   else printf("I am NOT OK!\n");

}

For the above example,  we will see "I am NOT OK!". Why, the reason is in this example my_enum is regarded as unsigned int. Therefore, it will be converted into unsigned before the comparison. After the conversion, it will always be non-negative so the if will take the else branch. But what about this code snippet (just a little bit modification):

enum MY_ENUM{
  MY_OK = 0,
  MY_NOT_OK
};

void foo()
{
   int i = -1;
   enum My_ENUM my_enum = MY_OK;

   if( i < MY_OK) printf("I am OK!\n");
   else printf("I am NOT OK!\n");

}

It will print "I am OK!" instead. The reason is  in this example  MY_OK  is regarded as int.

Why we will see results like this? It is kind of confusing. To find out the things under-hood, we need to look at the C standard. There are several sections related to our problem.

  • Section 6.4.4.3 says enumerators have type int. 
  • Section 6.7.2.2/4 states that the enumeration type has a compatible type capable of representing the values of all enumerator members.
  • Section 6.7.2.2/2 suggests that an enumerator initialized with a constant expressionthat falls outside the range of an int has unspecified or undefined behavior.
Section 6.7.2.2/4 can be used to explain the first example. The compatible type capable of representing the values of all enumerator members is unsigned it, since we only need to represent 0 and 1.Therefore   my_enum is unsigned int. Section 6.4.4.3 can explain the second example since MY_OK is an enumerator.

Then what if you want enumeration type to be int? Just follow Section 6.7.2.2/4 and have the enumeration defined as follow:

enum MY_ENUM{
  MY_NEG = -1
  MY_OK = 0,
  MY_NOT_OK
};

since we only need to represent -1,0 and 1 here, the enumeration type needs to be int.

We should be careful about Section 6.7.2.2/2. Since it suggests if we define something as follow, the behavior will be unspecified.

enum MY_ENUM{
  MY_NEG = -1
  MY_OK = 0,
  MY_BIG = 0x80000000
};
0x80000000 can't be represented in a 32bit int. Though in gcc, the enumeration type is still int here, we cannot expect a uniform behavior on other compilers.

2 comments:

  1. The type of an enumeration is "implementation-defined"; on a different compiler, it might be signed (see http://stackoverflow.com/questions/159034/). So your first example will behave differently on different platforms.

    The type of an enumeration constant is always "int" (C standard section 6.4.4.3), so your second example will behave the same on all platforms.

    If you want to force your enum to be signed, you can do this:

    enum MY_ENUM {
    MY_ENUM_FORCE_SIGNED=-1,
    MY_OK,
    MY_NOT_OK
    };

    By the way, the comma after the last element in your enum is technically a syntax error. GCC will inform you of this if you specify "-pedantic".

    ReplyDelete
    Replies
    1. Thanks for your great comments, Nemo. I also did some further investigation and updated the original posts. There are also Section 6.7.2.2/4 and 6.7.2.2/2 related to enum.

      Delete