11

In my code I'm used to write fall-back default cases containing asserts like the following, to guard me against forgetting to update the switch in case semantics change

switch(mode) {
case ModeA: ... ;
case ModeB: ... ;
case .. /* many of them ... */
default: {
  assert(0 && "Unknown mode!");
  return ADummyValue();
}
};

Now I wonder whether the artificial fall-back check default case will interfere with jump table generations? Imagine "ModeA" an "ModeB" etc are consecutive so the compiler could optimize into a table. Since the "default" case contains an actual "return" statement (since the assert will disappear in release mode and the compiler will moan about a missing return statement), it seems unlikely the compiler optimizes the default branch away.

What's the best way to handle this? Some friend recommended me to replace "ADummyValue" with a null pointer dereference, so that the compiler, in presence of undefined behavior, could omit to warn about a missing return statement. Are there better ways to solve this?

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212

7 Answers7

3

At least with the compilers I've looked at, the answer is generally no. Most of them will compile a switch statement like this to code roughly equivalent to:

if (mode < modeA || mode > modeLast) {
    assert(0 && "Unknown mode!");
    return ADummyValue();
}
switch(mode) { 
    case modeA: ...;
    case modeB: ...;
    case modeC: ...;
    // ...
    case modeLast: ...;
}
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • What a shame. I have to sacrifice validation for speed :( – Johannes Schaub - litb Nov 25 '10 at 16:55
  • @Johannes: would an array of function pointers work similarly as jump tables? Look up the function pointer from the array and call it. (jump table consist of one register jump and one fixed jump; array of function pointer would be one register load and one register jump, I suppose?) – rwong May 26 '11 at 07:26
3

If your compiler is MSVC, you can use __assume intrinsic : http://msdn.microsoft.com/en-us/library/1b3fsfxw(v=VS.80).aspx

ruslik
  • 14,714
  • 1
  • 39
  • 40
2

if you're using "default" (ha!) <assert.h>, the definition's tied to the NDEBUG macro anyway, so maybe just

    case nevermind:
#if !defined(NDEBUG)
    default:
        assert("can" && !"happen");
#endif
    }
  • this is what I recommend. It doesn't sacrifice speed, you can still test for unaccounted types, and you can still get compiler warnings – Trevor Hickey May 04 '12 at 09:38
1

I only see 1 solution in case the optimization actually is disturbed: the infamous "#ifndef NDEBUG" round the default case. Not the nicest trick, but clear in this situation.

BTW: did you already have a look what your compiler does with and without the default case?

stefaanv
  • 14,072
  • 2
  • 31
  • 53
1

If you have a state that should never be reached, then you should kill the program, because it just reached an unexpected state, even in the release mode (you might just be more diplomatic and actually save users data and do all that other nice stuff before going down).

And please don't obsess over micro optimizations unless you actually have measured (using a profiler) that you need them.

Šimon Tóth
  • 35,456
  • 20
  • 106
  • 151
  • But I don't want to check for the unreachable state in release mode. My assumption is that the code contains no bugs. Of course that is very likely a false assumption, but It seems needed to me to get rid of the check for the unreachable state (and this is very performance critical: Name lookup routine, implicit conversion routine, etc of a scripting language runtime). – Johannes Schaub - litb Nov 25 '10 at 16:04
1

The best way to handle this is not to disable the assert. That way you can also keep an eye on possible bugs. Sometimes it is better for the application to crash with a good message explaining what exactly happened, then to continue working.

BЈовић
  • 62,405
  • 41
  • 173
  • 273
0

Use compiler extensions:

// assume.hpp
#pragma once

#if defined _MSC_VER
#define MY_ASSUME(e) (__assume(e), (e) ? void() : void())
#elif defined __GNUC__
#define MY_ASSUME(e) ((e) ? void() : __builtin_unreachable())
#else   // defined __GNUC__
#error unknown compiler
#endif  // defined __GNUC__

-

// assert.hpp
#include <cassert>
#include "assume.hpp"

#undef MY_ASSERT
#ifdef NDEBUG
#define MY_ASSERT MY_ASSUME
#else   // NDEBUG
#define MY_ASSERT assert
#endif  // NDEBUG
Timothy003
  • 2,348
  • 5
  • 28
  • 33