40

Consider the following enum and switch statement:

typedef enum {
    MaskValueUno,
    MaskValueDos
} testingMask;

void myFunction(testingMask theMask) {
    switch (theMask) {
        case MaskValueUno: {}// deal with it
        case MaskValueDos: {}// deal with it
        default: {} //deal with an unexpected or uninitialized value
    }
};

I'm an Objective-C programmer, but I've written this in pure C for a wider audience.

Clang/LLVM 4.1 with -Weverything warns me at the default line:

Default label in switch which covers all enumeration values

Now, I can sort of see why this is there: in a perfect world, the only values entering in the argument theMask would be in the enum, so no default is necessary. But what if some hack comes along and throws an uninitialized int into my beautiful function? My function will be provided as a drop in library, and I have no control over what could go in there. Using default is a very neat way of handling this.

Why do the LLVM gods deem this behaviour unworthy of their infernal device? Should I be preceding this by an if statement to check the argument?

Deduplicator
  • 8,591
  • 5
  • 31
  • 50
Swizzlr
  • 503
  • 1
  • 5
  • 8
  • 1
    I should say, my reason for -Weverything is to make myself a better programmer. [As the NSHipster says](http://nshipster.com/pragma/): `"Pro tip: Try setting the -Weverything flag and checking the “Treat Warnings as Errors” box your build settings. This turns on Hard Mode in Xcode."`. – Swizzlr Dec 13 '12 at 12:36
  • 2
    `-Weverything` can be useful, but be careful about mutating your code too much to deal with it. Some of those warnings are not only worthless but counter-productive, and are best turned off. (Indeed, that's the use case for `-Weverything`: start with it on, and turn off what doesn't make sense.) – Steven Fisher Dec 13 '12 at 17:50
  • Could you expand a bit on the warning message for posterity? There's usually more to them than that. – Steven Fisher Dec 13 '12 at 17:55
  • After reading the answers, I still prefer your initial solution. Usage of default statement IMHO is better than the alternatives given in the answers. Just a small note: the answers are really good and informative, they show a cool way how to solve the problem. – bbaja42 Dec 13 '12 at 17:56
  • @StevenFisher That was the entire warning. And as Killian pointed out if I modify my enum later it would open the possibility for valid values to pass to the default implementation. It seems to be a good design pattern (if it is such a thing). – Swizzlr Dec 13 '12 at 19:07
  • For completeness, the flag responsible for this is `-Wcovered-switch-default`. – n-b Dec 14 '12 at 09:21

8 Answers8

35

Here's a version that suffers from neither the problem clang's reporting or the one you're guarding against:

void myFunction(testingMask theMask) {
    assert(theMask == MaskValueUno || theMask == MaskValueDos);
    switch (theMask) {
        case MaskValueUno: {}// deal with it
        case MaskValueDos: {}// deal with it
    }
}

Killian has explained already why clang emits the warning: if you extended the enum, you'd fall into the default case which probably isn't what you want. The correct thing to do is to remove the default case and get warnings for unhandled conditions.

Now you're concerned that someone could call your function with a value that's outside the enumeration. That sounds like failing to meet the function's prerequisite: it's documented to expect a value from the testingMask enumeration but the programmer has passed something else. So make that a programmer error using assert() (or NSCAssert() as you said you're using Objective-C). Make your program crash with a message explaining that the programmer is doing it wrong, if the programmer does it wrong.

  • 6
    +1. As a small modification, I prefer to have each case `return;` and add an `assert(false);` to the end (instead of repeating myself by listing the legal enums in an initial `assert()` and in the `switch`). – Josh Kelley Dec 13 '12 at 15:08
  • 1
    What if the incorrect enum isn't passed by a dumb programmer, but rather by a memory corruption bug? When the driver presses the break of their Toyota and a bug has corrupted the enum, then the break-handling program should crash & burn, and there should be a text displayed to the driver: "the programmer is doing it wrong, bad programmer!". I don't quite see how this will help the user, before they drive over the edge of a cliff. –  Dec 13 '12 at 15:28
  • 2
    @Lundin is that what the OP is doing, or is that a pointless theoretical edge case you've just constructed? Regardless, a "memory corruption bug" [i] is programmer error, [ii] is not something you can continue from in a meaningful way (at least not with the requirements as stated in the question). –  Dec 13 '12 at 15:33
  • *is probably programmer error. Unless the computer is in a satellite (in which case it isn't under a Toyota, obviously) programmer error is more likely to corrupt a value on the stack than a cosmic ray. Probably even in the satellite case, in fact. –  Dec 13 '12 at 15:44
  • 1
    @GrahamLee I'm just saying that "lay down and die" isn't necessarily the best option when you spot an unexpected error. –  Dec 13 '12 at 16:48
  • @Lundin: Another way to handle this defensively is to use the middle ground. Assert in debug builds, so that during development and testing no one uses the function incorrectly and thus prevents against programmer errors. The assert won't fire in release builds and thus the app will not crash. However, as others have pointed out, if you have memory corruption the app *will* eventually run into trouble, so it's still better to crash/quit at first sign of trouble. In such a case, the crash logs have a somewhat better chance of being meaningful. – Umsd Dec 14 '12 at 01:39
  • 1
    It has the new problem that you might let the assertion and the cases get out of sync accidentally. – user253751 Aug 01 '16 at 04:41
  • A `default: assert(theMask == MaskValueDos)` as the second case would have been better than listing all values twice. – Deduplicator Aug 01 '16 at 08:33
  • The frustrating thing is that if the function returns a value and you remove the `default` label, GCC complains `control reaches end of non-void function` even though all of the enum values are used in the `switch`. – Nathan Osman Dec 27 '17 at 06:53
  • A production assert, especially in a real-time/embedded system is usually an unwise move... better to trap the dubious condition, and do something to recover. This is why optimization of default clauses is not a great move, but C permits it :( – Andrew Dec 21 '20 at 12:18
9

Having a default label here is an indicator that you're confused about what you're expecting. Since you have exhausted all possible enum values explicitly, the default cannot possibly be executed, and you don't need it to guard against future changes either, because if you extended the enum, then the construct would already generate a warning.

So, the compiler notices that you have covered all bases but appear to be thinking that you haven't, and that is always a bad sign. By taking the minimal effort to change the switch to the expected form, you demonstrate to the compiler that what you appear to be doing is what you are actually doing, and you know it.

Kilian Foth
  • 107,706
  • 45
  • 295
  • 310
  • 2
    But my concern is a dirty variable, and guarding against that (such as, like I said, an uninitialized int). It seems to me that it would be possible for the switch statement to reach default in such a situation. – Swizzlr Dec 13 '12 at 12:45
  • I don't know the definition for Objective-C by heart, but I assumed that a typdef'd enum would be enforced by the compiler. In other words, an "uninitialized" value could only enter the method if your program *already* exhibits undefined behaviour, and I find it entirely reasonable that a compiler doesn't take that into account. – Kilian Foth Dec 13 '12 at 12:57
  • 3
    @KilianFoth no, they aren't. Objective-C enums are C enums, not Java enums. Any value from the underlying integral type _could_ be present in the function argument. –  Dec 13 '12 at 13:02
  • 2
    Also, enums could be set in runtime, from integers. So they can contain any value imaginable. –  Dec 13 '12 at 15:21
  • OP is correct. testingMask m; myFunction(m); would most likely hit the default case. – Matthew James Briggs Nov 01 '15 at 05:38
5

Clang is confused, having a default statement there is perfectly fine practice, it is known as defensive programming and is considered good programming practice (1). It is used plenty in mission-critical systems, though perhaps not in desktop programming.

The purpose of defensive programming is to catch unexpected errors that in theory would never happen. Such an unexpected error is not necessarily the programmer giving the function an incorrect input, or even an "evil hack". More likely, it could be caused by a corrupt variable: buffer overflows, stack overflow, runaway code and similar bugs not related to your function could be causing this. And in case of embedded systems, variables could possibly change because of EMI, particularly if you are using external RAM circuits.

As for what do write inside the default statement... if you suspect that the program has gone haywire once you ended up there, then you need some sort of error handling. In many cases you can probably just simply add an empty statement with a comment: "unexpected but doesn't matter" etc, to show that you have given thought to the unlikely situation.


(1) MISRA-C:2004 15.3.

  • 1
    Please not that the average PC programmer typically finds the concept of defensive programming completely alien, since their view of a program is an abstract utopia where nothing that can be wrong in theory will go wrong in practice. Therefore you will get quite diverse answers to your question depending on who you ask. –  Dec 13 '12 at 15:19
  • 3
    Clang is not confused; it has been explicitly asked to provide _all_ warnings, which includes ones that are not always useful, such as stylistic warnings. (I personally opt into this warning because I don't want defaults in my enum switches; having them means I don't get a warning if I add an enum value and don't handle it. For this reason, I always handle the bad value case outside the enum.) – Jens Ayton Dec 13 '12 at 18:14
  • 3
    @JensAyton It is confused. All professional static analyser tools as well as all MISRA-compliant compilers will give a warning if a switch statement _lacks_ a default statement. Try one yourself. –  Dec 13 '12 at 19:08
  • 5
    Coding to MISRA-C is not the only valid use for a C compiler, and, again, `-Weverything` turns on _every_ warning, not a selection appropriate to a particular use case. Not fitting your taste isn’t the same thing as confusion. – Jens Ayton Dec 13 '12 at 19:46
  • 1
    @JensAyton MISRA-C is not a "use case", it is only concerned with writing safe, bug-free C programs. I don't think many programmers are aiming to write unsafe, unstable, buggy programs. Defensive programming makes sense in every kind of program, it is always better to report unexpected errors and handle them, as far as possible, rather than not detecting them and letting the program crash. –  Dec 13 '12 at 20:30
  • 3
    @Lundin: I'm pretty sure sticking to MISRA-C does not magically make programs safe and bug-free...and i'm equally sure that there is plenty of safe, bug-free C code from people who have never even *heard* of MISRA. In fact, it's little more than someone's (popular, but not undisputed) opinion, and there are people who find some of its rules arbitrary and sometimes even harmful outside of the context for which they were designed. – cHao Dec 14 '12 at 14:28
  • 1
    @cHao Of course there is no simply cure that fixes every programming problem in the world. But the thing is, MISRA-C isn't just arbitrary opinions of fuzzy garage programmers, it is based on scientific evidence. There are plenty of computer scientists who have proven that using a formal coding standard, be it MISRA-C or something else, increases the quality and reduces the bugs rather dramatically. Feel free to argue with the leading computer scientists of the world. Personally I don't consider myself quite in their league, so I just follow their research. –  Dec 14 '12 at 15:42
  • @Lundin: MISRA-C was developed with a particular niche in mind: *embedded systems*. You know, software that's big-designed up front and could kill someone if someone doesn't catch odd cases. Rules geared toward that type of development are not always as applicable for software that changes on a regular basis. But the cargo cultists are more than happy to avoid thinking about such things before misapplying them. So yeah, i'll argue with anyone who tells me their way is the only way to write software, especially if they can't tell me why. – cHao Dec 18 '12 at 13:57
  • @cHao Roughly, MISRA-C contains this: ensure that the code follows the standard, remove reliance on undefined/unspecified/impl.defined behavior, minimize weird obfuscation like function-like macros, pre-processor, trigraphs, minimize spaghetti code that is jumping all over the place, ensure that a number of classic bugs don't appear (array out of bounds, uninitialized variables, returning pointers to local data etc etc), educate about implicit type promotions and minimize bugs related to that. –  Dec 18 '12 at 14:04
  • @Lundin: Note how none of that says "Assume enums will magically sprout new values". – cHao Dec 18 '12 at 14:05
  • @cHao Now, do you see anything among the things mentioned that is specific to embedded systems? Do you see anything that doesn't apply to any form of C program? Is there some weird niche of PC programming where undefined behavior is encouraged? Is there some sort of smart phone app where the coding standard says: write spaghetti code whenever possible? –  Dec 18 '12 at 14:06
  • @Lundin: If that's all it contained, it'd be useless. No, it contains *specific* recommendations, which you don't get to handwave away. Each of those recommendations should be weighed in the context where it is to be applied, rather than just blindly accepting what some guy said about a whole different type of development some ages ago just cause they're smart. – cHao Dec 18 '12 at 14:12
  • @cHao Regarding defensive programming, there are several MISRA rules that exist to catch unintended bugs caused by the programmer, but also to catch phenomenons like runaway code, out of bounds access etc. These kind of bugs are not exclusive to embedded systems, though the effects are far more severe. As for the specific case of enum, chances are high that the programmer causes various bugs, such as the classic `enum {A, B, C=1}; if(x == C) // true when x == b` (MISRA 9.3), or even more likely, because enum variables and enum constants don't necessarily have the same width and signedness. –  Dec 18 '12 at 14:13
  • @cHao Have you even read the MISRA standard? Care to cite the specific recommendations you refer to? –  Dec 18 '12 at 14:14
  • @Lundin: That specific case won't be caught by a `default` -- in fact, it'd be *hidden*, unless you try to catch every named case. If i see a `default`, and no `case C:`, i assume the `default` branch happens when x == C. But no, `case B` does. – cHao Dec 18 '12 at 14:16
  • How about the one we're arguing about? :P – cHao Dec 18 '12 at 14:17
  • @cHao Alright let me come up with one example. Suppose we have `typedef enum { X=INT_MAX } type;`. The enum constant X is guaranteed to be the same as an int (C11 6.7.2.2/1). Then we write a function `void func(type t) { switch(t){ ...` and we only handle the case where t==X. If no default switch statement is present, the program may then merrily run amok, because an enum variable is not guaranteed to be large enough to hold an enum constant (C11 6.7.2.2/4). If the enum variable is only 1 byte large, the assignment of t = INT_MAX; may pass without warnings, implicit truncation. –  Dec 18 '12 at 14:34
  • This is perfectly fine as far as the C standard is concerned, it is a programmer mistake, it is not specific to a certain type of application, and the only way to catch it is static analysis by the compiler or a 3rd party tool - a MISRA-C compliant one would have caught the bug. –  Dec 18 '12 at 14:36
  • @Lundin: "The choice of type is implementation-defined, but *shall be capable of representing the values of all the members of the enumeration.*" (6.7.2.2/4) – cHao Dec 18 '12 at 14:41
  • That means your enum variable *is* guaranteed to be large enough to hold any member of the enum. In this case, it has to be at least an `int`. The only thing the implementation has to decide on is whether they're signed or not. – cHao Dec 18 '12 at 15:02
  • @cHao Alright, but it doesn't really matter, the point is that the programmer is free to show any integer into an enum, whether it fits or not. Also, if enums where 1 byte large, they would be subject to implicit integer promotions that may change their signedness in other ways than intended (which is particularly dangerous if you mix them with raw hexadecimal literals). –  Dec 18 '12 at 15:28
  • @Lundin: I don't have a copy of the MISRA-C stuff available, but there better be something in it about treating enums as *enums*. :P If enums were one byte in size, then the variables and values would be as well. You only run into issues if you're treating enums as numbers or vice versa, or arbitrarily treating one enum type as another. And either one should in itself be against any decent coding standard. – cHao Dec 18 '12 at 16:15
  • @cHao If the C language allows it, the C programmers tend to do it. I've seen all kinds of weird hacks related to enums. MISRA-C has a whole chapter related to rules against implicit promotions, there is a concept of "underlying type", enforcing you to treat every variable as the intended type, and add explicit casts to prevent various goof-ups like this one. But the defensive programming rules of MISRA would have caught the bug as well, there are typically several rules slightly overlapping (probably unintentionally), preventing one particular bug in many different ways. –  Dec 18 '12 at 16:20
  • @Lundin: My point at the beginning of all this was that MISRA-C is not the only way to write clean code. There are other coding standards that work just as well. And in a lot of them, enums (other than flags) are considered to be a limited set of values. Any value not in the set would not even be allowed to exist, making a `default` case unreachable in compliant code if you've already accounted for all the allowed values. – cHao Dec 18 '12 at 18:37
  • "It is used plenty in mission-critical systems, though perhaps not in desktop programming." Yeah, because obviously the behavior of any software that runs on a computer located on a desk doesn't matter and the user nor his employer cares whether it will crash or yield bogus results. – Martin Maat Jan 27 '19 at 13:08
4

But what if some hack comes along and throws an uninitialized int into my beautiful function?

Then you get Undefined Behavior, and your default will be meaningless. There's nothing that you can possibly do to make this any better.

Let me be more clear. The instant someone passes an uninitialized int into your function, it is Undefined Behavior. Your function could solve the Halting Problem and it wouldn't matter. It is UB. There is nothing you can possibly do once UB has been invoked.

doubleYou
  • 2,737
  • 1
  • 11
  • 25
DeadMG
  • 36,794
  • 8
  • 70
  • 139
  • 3
    How about logging it or throw an exception instead if silently ignore it? – jgauffin Dec 13 '12 at 13:26
  • 4
    Indeed, there is a lot that can be done, such as... adding a default statement to the switch and gracefully handle the error. This is known as _defensive programming_. It will not only protect against the dumb programmer handing the function incorrect inputs, but also against various bugs caused by buffer overflow, stack overflow, runaway code etc etc. –  Dec 13 '12 at 15:07
  • 1
    No, it won't handle anything. It is UB. The program has UB the moment the function is entered, and the function body can do nothing about it. – DeadMG Dec 14 '12 at 13:11
  • 5
    @DeadMG An enum can have any value that its corresponding integer type in the implementation might have. There is nothing undefined about it. Accessing the contents of an uninitialized auto variable is indeed UB, but the "evil hack" (which is more likely some sort of bug) might as well toss a well-defined value to the function, even though it is not one of the values listed in the enum declaration. –  Dec 14 '12 at 15:47
4

Better still:

typedef enum {
    MaskValueUno,
    MaskValueDos,

    MaskValue_count
} testingMask;

void myFunction(testingMask theMask) {
    assert(theMask >= 0 && theMask<MaskValue_count);
    switch theMask {
        case MaskValueUno: {}// deal with it
        case MaskValueDos: {}// deal with it
    }
};

This is less error-prone when adding items to the enum. You can skip the test for >= 0 if you make your enum values unsigned. This method only works if you have no gaps in your enum values, but that is often the case.

RiaD
  • 1,700
  • 2
  • 12
  • 13
  • 2
    This is polluting the type with values you simply don't want that the type contains. It has similarities with the good old WTF here: http://thedailywtf.com/articles/What_Is_Truth_0x3f_ – Martin Sugioarto Sep 21 '16 at 10:09
2

The default statement wouldn't necessarily help. If the switch is over an enum, any value that is not defined in the enum will end up executing undefined behaviour.

For all you know, the compiler can compile that switch (with the default) as:

if (theMask == MaskValueUno)
  // Execute something MaskValueUno code
else // theMask == MaskValueDos
  // Execute MaskValueDos code

Once you trigger undefined behaviour, there is no going back.

Myself
  • 29
  • 1
  • 2
    Firstly, it’s an _unspecified value_, not undefined behaviour. This means the compiler may assume some other value that’s more convenient, but it may not turn the moon into green cheese, etc. Secondly, as far as I can tell, that only applies to C++. In C, the range of values of an `enum` type is the same as the range of values of its underlying integer type (which is implementation-defined, hence must be self-consistent). – Jens Ayton Dec 13 '12 at 18:34
  • 1
    However, an instance of an enum variable and an enumeration constant do not necessarily need to have the same width and signedness, they are both impl.defined. But I'm pretty sure that all enums in C are evaluated exactly like their corresponding integer type, so there is no undefined or even unspecified behavior there. –  Dec 13 '12 at 19:17
2

I also prefer to have a default: in all cases. I'm late to the party as usual, but... some other thoughts that I didn't see above:

  • The particular warning (or error if also throwing -Werror) is coming from -Wcovered-switch-default (from -Weverything but not -Wall). If your moral flexibility allows you to turn off certain warnings (i.e., excise some things from -Wall or -Weverything), consider throwing -Wno-covered-switch-default (or -Wno-error=covered-switch-default when using -Werror), and in general -Wno-... for other warnings you find disagreeable.
  • For gcc (and more generic behavior in clang), consult gcc manpage for -Wswitch, -Wswitch-enum, -Wswitch-default for (different) behavior in similar situations of enumerated types in switch statements.
  • I also don't like this warning in concept, and I don't like its wording; to me, the words from the warning ("default label ... covers all ... values") suggest that the default: case will be always be executed, such as

    switch (foo) {
      case 1:
        do_something(); 
        //note the lack of break (etc.) here!
      default:
        do_default();
    }
    

    On first reading, this is what I thought you were running into -- that your default: case would always be executed because there's no break; or return; or similar. This concept is similar (to my ear) to other nanny-style (albeit occasionally helpful) commentary that spews forth from clang. If foo == 1, both functions will be executed; your code above has this behavior. I.e., fail to break-out only if you want to possibly keep executing code from subsequent cases! This appears, however, not to be your problem.

At the risk of being pedantic, some other thoughts for completeness:

  • I do however think this behavior is (more) consistent with aggressive type checking in other languages or compilers. If, as you hypothesize, some reprobate does attempt to pass an int or something to this function, which is explicitly intending to consume your own specific type, your compiler should protect you equally well in that situation with an aggressive warning or error. BUT it doesn't! (That is, it seems that at least gcc and clang don't do enum type-checking, but I hear that icc does). Since you aren't getting type-safety, you could get value-safety as discussed above. Otherwise, as suggested in TFA, consider a struct or something that can provide type-safety.
  • Another workaround could be creating a new "value" in your enum such as MaskValueIllegal, and not supporting that case in your switch. That would be eaten by the default: (in addition to any other wacky value)

Long live defensive coding!

hoc_age
  • 121
  • 2
1

Here's an alternative suggestion:
The OP is trying to protect against the case where someone passes in an int where an enum is expected. Or, more likely, where someone has linked an old library with a newer program using a newer header with more cases.

Why not change the switch to handle the int case? Adding a cast in front of the value in the switch eliminates the warning and even provides a degree of hint about why the default exists.

void myFunction(testingMask theMask) {
    int maskValue = int(theMask);
    switch(maskValue) {
        case MaskValueUno: {} // deal with it
        case MaskValueDos: {}// deal with it
        default: {} //deal with an unexpected or uninitialized value
    }
}

I find this much less objectionable than the assert() testing each of the possible values or even making the assumption that the range of enum values is well-ordered so that a simpler test works. This is just an ugly way of doing what default does precisely and beautifully.

Richard
  • 21
  • 1
  • 1
    this post is rather hard to read (wall of text). Would you mind [edit]ing it into a better shape? – gnat Jun 03 '14 at 16:20