Better Alternatives to Assert(False) in C/C++

Currently, I write

assert(false);

at places that my code is never supposed to reach. One example, in a very C-ish style, is:

int findzero( int length, int * array ) {
  for( int i = 0; i < length; i++ )
    if( array[i] == 0 )
      return i;
  assert(false);
}

My compiler recognizes that the program finishes once assert(false) has been reached. However, whenever I compile with -DNDEBUG for performance reasons, the last assertion vanishes and the compiler warns that the execution finishes the function without a return statement.

What are better alternatives of finishing off a program if a supposedly unreachable part of the code has been reached? The solution should

  • be recognized by the compiler and not produce warnings (like the ones above or others)
  • perhaps even allow for a custom error message.

I am explicitly interested in solutions no matter whether it's modern C++ or like 90s C.

8

8 Answers

Replacing your assert(false) is exactly what "unreachable" built-ins are for.

They are a semantic equivalent to your use of assert(false). In fact, VS's is spelt very similarly.

GCC/Clang/Intel:

__builtin_unreachable()

MSVS:

 __assume(false)

These have effect regardless of NDEBUG (unlike assert) or optimisation levels.

Your compiler, particularly with the above built-ins but also possibly with your assert(false), nods its head in understanding that you're promising that part of the function will never be reached. It can use this to perform some optimisations on certain code paths, and it will silence warnings about missing returns because you've already promised that it was deliberate.

The trade-off is that the statement itself has undefined behaviour (much like going forth and flowing off the end of the function was already). In some situations, you may instead wish to consider throwing an exception (or returning some "error code" value instead), or calling std::abort() (in C++) if you want to just terminate the program.


There's a proposal (P0627R0), to add this to C++ as a standard attribute.


From the GCC docs on Builtins:

If control flow reaches the point of the __builtin_unreachable, the program is undefined. It is useful in situations where the compiler cannot deduce the unreachability of the code. [..]

9

As a fully portable solution, consider this:

[[ noreturn ]] void unreachable(std::string_view msg = "<No Message>") {
    std::cerr << "Unreachable code reached. Message: " << msg << std::endl;
    std::abort();
}

The message part is, of course, optional.

2

I like to use

assert(!"This should never happen.");

...which can also be used with a condition, as in

assert(!vector.empty() || !"Cannot take element from empty container." );

What's nice about this is that the string shows up in the error message in case an assertion does not hold.

3

I recommend C++ Core Gudelines's Expects and Ensures. They can be configured to abort (default), throw, or do nothing on violation.

To suppress compiler warnings on unreachable branches you can also use GSL_ASSUME.

#include <gsl/gsl>

int findzero( int length, int * array ) {
  Expects(length >= 0);
  Expects(array != nullptr);

  for( int i = 0; i < length; i++ )
    if( array[i] == 0 )
      return i;

  Expects(false);
  // or
  // GSL_ASSUME(false);
}

I use a custom assert that turns into __builtin_unreachable() or *(char*)0=0 when NDEBUG is on (I also use an enum variable instead of a macro so that I can easily set NDEBUG per scope).

In pseudocode, it's something like:

#define my_assert(X) do{ \ 
       if(!(X)){ \
           if (my_ndebug) MY_UNREACHABLE();  \
           else my_assert_fail(__FILE__,__LINE__,#X); \
       } \
     }while(0)

The __builtin_unreachable() should eliminate the warning and help with optimization at the same time, but in debug mode, it's better to have an assert or an abort(); there so you get a reliable panic. (__builtin_unreachable() just gives you undefined behavior when reached).

3

assert is meant for scenarios that are ACTUALLY supposed to be impossible to happen during execution. It is useful in debugging to point out "Hey, turns out what you thought to be impossible is, in fact, not impossible." It looks like what you should be doing in the given example is expressing the function's failure, perhaps by returning -1 as that would not be a valid index. In some instances, it might be useful to set errno to clarify the exact nature of an error. Then, with this information, the calling function can decide how to handle such error.

Depending on how critical this error is to the rest of the application, you might try to recover from it, or you might just log the error and call exit to put it out of its misery.

I believe the reason you are getting the errors is because assertions are generally used for debugging on your own code. When these functions are run in release, exceptions should be used instead with an exit by std::abort() to indicate abnormal program termination.

If you still want to use asserts, there is an answer about defining a custom one by PSkocik, as well as a link here where someone proposes the use of custom asserts and how to enable them in cmake here as well.

One rule that is sometimes found in style-guides is

"Never return from the middle of a function"
All functions should have a single return, at the end of the function.

Following this rule, your code would look like:

int findzero( int length, int * array ) {
  int i;
  for( i = 0; i < length; i++ )
  {
    if( array[i] == 0 )
      break;             // Break the loop now that i is the correct value
  }

  assert(i < length);    // Assert that a valid index was found.
  return i;              // Return the value found, or "length" if not found!
}
16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

James H. Sterling

James H. Sterling

Environmental Science & Climate Journalist

James Sterling reports on renewable energy developments, climate policy, ecological conservation, and green tech innovations around the globe.

Share this article
Twitter Facebook Pinterest