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.
September 8, 2009 at 4:13 pm |
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.
September 9, 2009 at 11:19 pm |
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.
September 9, 2009 at 11:27 pm |
Aaron: thank you for the comment, I was not aware of that!