Enums in C

A few months ago, in a general-purpose programming chat room, a few friends and I were arguing about the treatment of enumerated types in C. I had just watched one of the Stanford University videos on C++ where I learned that C++’s enums were real types and that they were strongly typed. I expressed my surprise and satisfaction on IRC about this fact and how I thought this was an improvement on their behavior in C. What followed was a shouting match where nobody listened to anybody and everybody just argued past one another about the treatment of enums in C.

I would like to explain in this post the problems I have with C’s enums without having to address the concerns of four different people at the same time. I wish to stress that those are the problems that I have with C’s enums, not the problems that C’s enums have.

First, a quick explanations of what enums are for the folks who don’t know C. Enums in C are a way to map identifiers to integral values. These identifiers can then be used to avoid magic numbers in your code. Here is a simple example:

enum Temperature { temp_low, temp_medium, temp_high };
enum Temperature t = temp_high;

The first line declares three “tags”, temp_low, temp_medium and temp_high which will be respectively mapped to the values 0, 1 and 2. (You can specify the exact value of a tag if you need to, but I won’t get into that, nor into the other subtleties, since they are irrelevant to the point I want to make.) The second line declares a variable of that enumerated type and assigns one of our tags.

One might ask why we don’t simply use #defines instead of enums; enums can be auto-enumerated, which is convenient, the type of the variable gives an indication of its usage, and certain compilers can emit a warning when you forget one of the tags in a switch/case statement. Beyond that, they’re pretty much the same.

I’m of the opinion that enumerations should create a new type and that the values of that type should be a set of “objects” that are completely distinct from the objects of other enumerations and from integers. In C, enums are not strongly typed. The following two declarations are perfectly valid:

enum Temperature t2 = 2;
int t3 = temp_high;

This is because enum Temperature is not a new type, it’s simply a set of textual tags that represent integers. You can mix and match ints and enums, and even with -Wall -Wextra -pedantic, gcc won’t raise a peep about the issue.

Even more insidious than that, you can mix and match different enums and you will not even get a warning. Consider the following code:

#include <stdio.h>

enum Direction { North, South, East, West };
enum Color { Red, Green, Blue };

void paint_screen(enum Color c) {
    switch(c) {
    case Red:
        puts("painting screen red");
        break;
    case Green:
        puts("painting screen green");
        break;
    case Blue:
        puts("painting screen blue");
        break;
    }
}

int main(void) {
    enum Direction d = South;

    paint_screen(d); /* oops! */

    return 0;
}

If you compile this code with -Wall -Wextra -pedantic, the compiler will remain silent, even though painting the screen South makes no sense.

In a language such as C++, enumerated types are their own types, not just integers with easy-to-remember names; the compiler would’ve caught that the argument passed to paint_screen was of the wrong type. This would raise an error that you’d be forced to fix to make the code compile.

The C apologists will argue that it’s not in the standard, that They Know What They’re Doing™, that it would make the language more complex, that doing bit-operations on enum values would be impossible, etc. I’m of the opinion that strongly-typed enums help make systems reliable, even if it seems like a trivial feature. A programming language’s target audience are programmers — people, not computers — and because programmers are fallible, the language should help making sure that silly mistakes are avoided.

About these ads

34 thoughts on “Enums in C

  1. I’m one of the people you call “C apologists”. I’m not a C apologists but I really think that strongly-typed enums are necessary. I agree with you about the fact that compilers should warn when the code is messing with enums AND when compiling option ask them to (not by default), but I also think that this case is really, really rare, if not inexistant.

    I’m also a huge fan of void* (see my http://voidstar.ws/ page ^^) and typecasting when necessary, because it simply bring true polymorphism and I don’t see how anybody could write code where this is really dangerous. Seriously I don’t see how type errors could be made, with an exception for drunk-coding maybe.

    I’d really like to see a good and real example of type error in a program. By real I mean a true mistake that leaded to a bug in a real program, not a made-up mistake for the explaination.

  2. An additional, unmentioned benefit of enums is that most common object file debug payloads (DWARF(for ELF files), COFF, ECOFF, PE/COFF (MS Windows)) contain the enum symbols and values which can then be displayed by enum symbol in some debuggers. Having the debugger show you the enum values displayed with their symbol is VERY handy when it’s available. With #define’d values, the token is vaporized in the C pre-processor.

    See here for more detail: http://codingrelic.geekhold.com/2008/10/ode-to-enum.html

    This is all completely tangential to gnuvince’s point that C enums are broken and should be fixed. I agree completely.

  3. There’s a very simple way to make enums “strong”:

    #include <stdio.h>
    
    typedef struct { enum { North, South, East, West } d; } Direction;
    typedef struct { enum { Red, Green, Blue } c; } Color;
    
    void paint_screen(Color c) 
     {
      switch(c.c) 
       {
       case Red:
        puts("painting screen red");
        break;
       case Green:
        puts("painting screen green");
        break;
       case Blue:
        puts("painting screen blue");
        break;
       }
     }
    
     int main(void) 
     {
      Direction d = {South};
      
      paint_screen(d); /* oops! */
    
      return 0;
     }
    

    Now the compiler complains with:

    z.c: In function ‘main’:
    z.c:26: error: incompatible type for argument 1 of ‘paint_screen’
    z.c:6: note: expected ‘Color’ but argument is of type ‘Direction’

  4. It would be nice if C had a C# type enum:

    enum Weapons { Sword, Gun, Axe }

    // somewhere
    if (player.weapon == Weapons.Sword)
    Console.WriteLine("It's a Sword!");

  5. Well we all know C is a mistake (no don’t want to listen to your buts). There’s too much software written in C, it just can’t die. I hate it and I have to live with it. How bad is that?

  6. No offense but you guys are not seeing the things the way they are.

    Enums are just integers for a very good reason, as everything else in C, type checking takes time, that’s why C++ and C# are much slower than Standard C.

    C is meant to be fast and that’s why enums are just a bunch of cute masks for some macro definitions, plus in most decent object oriented programing languages where you’d use enums, which gets to be defining types for qualitative parameters (event or alignment types for example), you would use integer constants.

    C is faster because while other languages check if things are ok C considers that they ARE and saves time with checking. It’s the developer’s obligation to ensure that they will be.

    That being said if you don’t like it just go to a higher level language instead of using C, it is not meant for high level purposes but to low level, high performance, super optimized coding.

    • 1. Having enums be proper types would not degrade runtime performance. It would not even effect runtime performance. After compilation, they would still be represented as integers. The only (and “only” may not be the right word, since it’s a big deal) benefit would be static checking at compile time. Catching more errors at compile time is a good thing.

      2. Compiling C isn’t particularly fast. The extra overhead for type-checking is pretty much insignificant. When you’re compiling C with optimizations turned on, the compilation time is entirely dominated by optimizations, NOT type checking.

      3. Proper enum types are not inconsistent with having low-level, high-performance, “super-optimized” programs. You can easily have both.

  7. Danjiel — C type checking is static type checking, done at compile time. Implementing the above would not impact runtime performance and would only minimally impact compile times.

    • I see…
      Well considering this would not affect execution time then I guess there are no reasons for this not being implemented.

      Still not a big deal anyways since all you’d gain is that it would avoid a few developer mistakes.
      But yeah it wouldn’t hurt anybody then. Still I’d suggest that it staid just a warning, perhaps while compiling with “-pedantic”.

      Sorry for being a little rude in my previous comment, I got a little mad about that guy who said “C is a mistake”, I really disliked that.

  8. C is the most awesome language around. If you dont like it then use C++ or C# or even python instead. C is for professional programmers not wussy girly high level language hobyists. Go away and come back another day.

  9. Sounds like you need a short lesson from Gruf and Grar, the cavemen from the computing past.

    - “Gruf?”
    - “Yeah Grar?”
    - “Don’t you think we try those pointy sticks the other guys are using? Like, to hunt from far”
    - “Why do that? Big clubs work. Big clubs go BOOM BOOM!”
    - “But Gruf, hunt from far. Hunt from hole. No boo boo on head.”
    - “Pointy stick for wussy girly guys. Breaks easily.”
    - “Maybe Gruf. Maybe.”

    … and then Gruf was killed by a sabertooth tiger on his next hunt. Grar ran away and joined a new tribe and started to hunt with smarter tools.

    Be Grar. Don’t be Gruf.

    • Funny, the way I heard the story Grar died a horrible death walking around with his friend Gruf one Sunday because his reliance on operating from a safe distance so much made him out of shape and stupid and he was easily taken by a stray jackal. Gruf, on the other hand, was constantly aware of his surroundings because of his daily need to confront real danger at close range and the resulting benefits to his cardiovascular health and reaction time allowed him to outran the jackal, leaving Grar to his fate.

      Point being, you can make a ludicrous analogy say whatever you want, so best to just talk about what you’re talking about.

      • Cool story, bro!

        I wrote this just to ridicule people like roughCuser which use terms like “wussy”, “girly” and such to describe high-level languages. Some people are not using those terms but still don’t do much to elevate the debate. All I picture while thinking of an answer to this kind of comment was people such as them where like cavemen grunting and farting while hacking K&R C on their green terminals.

        Programming is about using the right tools for the jobs and while there is still and obviously strong need for low-level languages, sticking to them just because they are the most widely used programming languages, or not wanting them to adopt a few higher-level idioms is not a way of thinking which will help programming evolve over time.

        I’m not saying people will start to use C# tomorrow, but that C, or whatever other language people will use in 20 years, will need to evolve in ways that will help programmer make less mistakes when they program, whatever their level.

      • Wow I should re-read myself before posting. Sorry for the weird phrasing. I did not want to sound like a caveman.

  10. Gruf died as a honorable club hunter, and died having faced every single animal he killed.
    He might be the last of his kind, but he was proud of standing for what he believed was the right path.

  11. Well, for some mysterious reason I prefer #define-s.

    I don’t see what your problem with assigning plain int-s to other plain int-s is, after all, what do you think you’ve really got inside a computer apart from equivalents of 0-s and 1-s? Using int-s is very convenient, even if their sizes and/or interpretations vary: they’re still plain 0-s and 1-s. Complain about compilers if you want, but the C language is perfect: you can go low or you can go high as needed, there’s so much flexibility in it that you can never cease wondering. :-P

    P.S.
    C++ is a mess, its only merit is that it can compile C. Java is a lot cleaner.

  12. @ahse: if you cannot differentiate two interpretations of ints, then C is not as flexible and cannot go as high as you want. And yes, I am fully aware that deep down in the machine, there’s only ints (and not even that), but it’s not very interesting, nor an appropriate level of abstraction for most applications.

  13. There are so many sites out there that do not properly explain enums. Thanks for this great explanation. Most sites just show how to declare them, and not how to actually USE them. This is great! Thanks.

  14. Hi,

    K&R says “Names in different enumerations must be distinct. Values need not be distinct in the same enumeration.”

    I understand that. And on Google I find vast amounts of discussion and explanation repeating it in many different ways. But what I’ve so far failed to find is an explanation of why this compiles without warning:

    #include
    enum why {ONE, TWO, THREE, ONE, TWO, THREE};
    int main(void)
    {
    printf(“%i”, ONE);
    return 0;
    }

    Why need names in the same enumeration not be distinct?
    Thanks.
    Mike

    • I’ve just checked your example and:
      “ch_why.c”, line 2.28: 1506-334 (S) Identifier ONE has already been defined on line 2 of “ch_why.c”.
      “ch_why.c”, line 2.33: 1506-334 (S) Identifier TWO has already been defined on line 2 of “ch_why.c”.
      “ch_why.c”, line 2.38: 1506-334 (S) Identifier THREE has already been defined on line 2 of “ch_why.c”.

      I am on AIX with compiler:
      IBM XL C/C++ for AIX, V11.1

  15. Many IDEs provide support for enums, so they can show you that the function you are typing takes a parameter of type color, even when color is just an enum, and then a list of color values.

  16. just arrived here by chance… Note that one important use of enums is as a basis a of greater set of value. E.g:
    1)
    enum rgb { red = 0×01 , green = 0×0001, blue = 0×000001 }
    enum saturation { invisible = 0 , light = 30, normal = 127 , …} ;
    For a typical usage:
    enum rgb col = strong * red + normal * green + light * blue ;
    (not going into endianess here, but you get the idea)

    2)
    management of UNIX file permissions: define the basic permissions, then define permissions on a file as a bitwise-or of these permissions…

  17. Pingback: C Enums | RM5248

  18. Pingback: How do you handle enum checks in function arguments?

  19. Came across this whilst looking for something else, but figured I’d try your example out in clang.

    > clang main.c
    main.c:23:18: warning: implicit conversion from enumeration type ‘enum Direction’ to different enumeration type ‘enum Color’ [-Wenum-conversion]
    paint_screen(d); /* oops! */
    ~~~~~~~~~~~~ ^
    1 warning generated.

    So it seems that clang does what you want, for what it’s worth :)

  20. The big problem with C++ style enums, in my experience, is that you can’t loop through the values! The ++ operator, or just adding 1, are not allowed.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s