Assertions in C++, and Why Not to Use assert()

It’s a given that error checking and self-consistency checking are essential in high-quality code. What is not a given is that assert() is a good way to achieve this. In fact, I would consider use of assert() and equivalent macros or functions as something to be discouraged in favour of more suitable alternatives. This article focuses on C++ because the language provides better facilities to replace assert() than C, but the sentiment applies to use of assert() in C too.

Background on assert()

assert() is intended to be used as a way to explicitly state that some condition should hold true in your code, otherwise something’s terribly wrong. Failing assertions are usually described as something that just can’t happen (ha!) in tested code.

The implementation of assert() in your compiler will look something similar to this:

The key points are:

  • If an asserted expression does not evaluate to true:
    • An error message containing the expression that was being checked, along with some source location information is printed to stderr or displayed as your C-runtime implementation chooses.
    • The program will then terminate abnormally, with no clean-up. On a POSIX system, SIGABRT will be raised. On both Windows and POSIX, the system may be configured to create a crash dump.
  • If the NDEBUG macro is defined, then the assertion will be compiled away to nothing. This means the assertion isn’t usually evaluated in release builds, but it depends on the build flags chosen by the developer.

Why assert() is a poor tool

Poor programmatic error reporting & handling

assert() terminates the program when it fails. That’s not a useful mechanism for callers to handle errors.

The distinction between reporting errors and abnormal process termination may not have meant much decades ago when C was used on UNIX for small programs that did their single job and then exited, but it is jarring in the modern reality where programs are large and complex, using library code and modules built by different people.

Just because someone passed some unexpected parameter to a library function or to a protocol command is not a good reason for my service, or indeed my entire OS, to go and restart. That operation should fail (i.e. return an error code, set a last-error value, throw an exception or whatever), and errors be logged, but all the other correctly working things the program is doing should keep on going, unless the error really is fatal, like the stack or heap being corrupted.

People will argue against this reasoning by insisting that assert() is only meant to be used for things that definitely won’t fail. To them I say, seriously, you’ve been programming how long and you still haven’t noticed how software tends to have lots of bugs in it?

Moreover, and I’m reluctant to point this out as I don’t want to be blaming the tools for people using them incorrectly, but the reality is that coding standards often encourage liberal use of assert(), so in practice people do use assert() when they should be using a proper error reporting mechanism. And after all, finding out (or adding) the right error code or exception for a given circumstance requires some effort, whereas I can be lazy and put in an assert() as my silver-bullet for all error-reporting needs and use the style-guide and inane proclamations of “It can’t fail if it works correctly!” as my shield from irritating suggestions that maybe it would be better if our critical service didn’t crash just because my function is given some input I either hadn’t anticipated or couldn’t be bothered to handle correctly.

Poor error logging

Just as failing operations should report errors usefully programmatically, useful information should be reported for the humans too, so they have a chance of fixing things.

assert() does give you a bit of context for the error, namely it will give you the failing expression code, and its location in the source. That’s certainly better than nothing. If you have crash dumps enabled, then you’ll get a crash dump too. Sounds good you say? Well the reality is more likely to be that you get nothing at all.

Firstly, the assertion message is going wherever your CRT wants it to go, which may be stderr that is being ignored, a message box that is clicked without reading it, or simply nowhere at all. Where it won’t go, is into your application’s log file (well OK if your log file is just redirection of stderr then it will, but besides that it won’t), which is the one place you really want it to go, so that you, your customer, or your support team can see what, when and where it failed.

And as for crash dumps, I can’t be sure, but I doubt that people usually run with them enabled. On Windows you have to jump through hoops with editing the registry to get crash dumps, and on Linux/UNIX systems they tend to be disabled by default too. You might ask your customer for a reproduction with crash dumps enabled, and they might even comply, but if your error took 3 months of continuous running to be provoked, then you’ve probably missed your chance to even understand what the nature of the bug is any time soon, never mind fix it, which won’t impress your customer.

If you’re going to put in an assertion in your code, you may as well make sure that you will be able to glean some useful logs when it’s actually hit.

Even if you do get the message from an assert() failing, assert() doesn’t exactly encourage you to put in useful information. Sure you get line/file (and possibly function name), but if that file has changed a lot, and you’re not sure which version of the code was actually being run, then that doesn’t help a lot, and the expression code may be completely useless altogether. For example, here’s a common idiom I see:

I can even sympathize with assert()-like behaviour in this case, as you probably have some state enumeration type and you’ve put in case handlers for each of the enumeration values, so really, that default shouldn’t be hit should it? But even knowing the line of code that has failed here, you miss out on the key piece of information that you will want if that assertion does fail – namely, what value did state have then?

People do sometimes put string literals into assert() as a means to display messages, rather than the ‘0’ you will get in the above example, e.g:

The need for playing games like this shows up an inadequacy of assert() (why not some kind of printf-style parameters after the expression so that you can create a formatted message along with the assertion failure?), and it doesn’t solve the problem of reporting what the bad state that caused the assertion failure actually was.

A final point on poor logging, is that the logging is particularly bad on Windows. The Visual C++ runtime will display a message box in most cases, which will be quite annoying in any kind of automation scenario where it blocks other processes, because your process won’t exit until someone clicks the “Abort” button. On the other hand, if your program is running as a service, you will just lose the assertion message altogether, and get a useless message in the event log about whatever structured exception code the runtime decided to give assertion failures.

Disabled in release builds

assert() doesn’t do anything if NDEBUG is defined. It’s a point of contention whether NDEBUG should be defined in release builds, and there are those that would not define NDEBUG, pointing out that you shouldn’t be shipping with code different from what you test, and I’d agree with them. Apart from the fact that NDEBUG will strip out a whole load of checking from your implementation, the things inside the assert() may well have side-effects. Here’s the archetypical example of that:

A pointer is initialized with some heap-allocated memory, and this is done inside an assert(), so that if malloc() fails, the program will terminate. Ignoring the matter of how best to report and handle memory allocation failures (or use of malloc() in a C++ program), this code has the problem that if NDEBUG is enabled, your call to malloc() won’t happen, so your release build will either crash or corrupt.

I’m not going to come down too strongly on that latter problem though, because I acknowledge that macros that conditionally do an operation have their place, e.g. for optimizing away code to format a string for a debug-log entry when a particular logging level is not enabled.

Alternatives to assert()

Static assertions

Before getting on to runtime assertions, let’s talk about static assertions. Static assertions check conditions at compile time, and fail the build if the asserted expression doesn’t evaluate to true. My view is, if you can get the compiler to check your work for you, you’re onto a good thing (and why I also think writing programs of more than a couple of thousands of lines, say, in languages that aren’t compiled is a bit of a step backwards technically, but that’s a discussion for another day).

C++11 introduces static_assert(), but you can get something similar from Boost in the form of BOOST_STATIC_ASSERT_MSG() if you’re stuck with a troglodyte compiler.

static_assert() takes two parameters, an expression to evaluate at compile-time, and a message to report if the expression doesn’t evaluate to true. C++17 apparently will make the message optional (which would be comparable to using BOOST_STATIC_ASSERT()), to the delight of lazy programmers everywhere.

If your assertion will work as a static assertion, then you should positively go ahead and write it as such. Some examples:

There are also a whole bunch of template classes in the type_traits header that let you statically check for properties of types, e.g. that they can be copy-constructed, that they are convertible to another type, that an integer type is unsigned, etc. These would also let you do compile-time validation of parameters to your own template classes.

Assertion exceptions

My recommendation would be that if you really do want an assertion scheme in your code, and can’t get by with just regular error reporting mechanisms of return codes, last-error codes and exceptions instead of this, that you use a custom assertion macro which throws an exception instead.

You then have the choice to log and handle this exception as you see fit, depending on context. If you get the exception inside the command-processor of some wire-protocol service, then you can fail that command and log the error details, but continue on. Similarly, if the exception is propagated out of some plugin that isn’t essential to the program’s operation, that plugin can be disabled for the rest of the program’s operation. In other contexts, such as if the exception is propagated back up to main(), or to the entry point of a thread, then you might just log the error to file and then abort() just as assert() would normally, but you will have given your destructors a chance to do a graceful shut-down along the way.

Here’s an example of an exception-throwing assertion implementation (you can also get this as the header file ThrowAssert.hpp from the Softwariness site on Github):

With the above throw_assert() implementation, the unexpected-state assertion can become:

If the default block is hit, an exception will be raised with a what() message along the lines of “Unexpected state value 1234: Unreachable code assertion failed in file ‘states.cpp’ line 21”. This gives you the key information you’ll want when investigating, namely what wonky state did it manage to get into. You’ll also notice that an expression of “false” gets prettified to “Unreachable code assertion” rather than “Assertion ‘false'” when reporting the error.

The assertion is also logged in the exception constructor, in case the exception is not caught further down the stack, and so that the error is logged at time of occurrence. By default, the logging at point-of-failure goes to stderr, but if THROWASSERT_LOGGER is defined before including ThrowAssert.hpp, a user-provided logging function can be substituted for this.

Custom fatal assertions

If you really must have assertions that will abort the process, then I would suggest that you create your own custom assertion macro (you can use the example assert() implementation at the beginning of this article as a starting point), called myapp_assert() or some such, which adds a message to your application’s own logging mechanism first before calling abort(). I’d recommend also supporting error messages to go along with the expression being asserted, like in my throw_assert() example above, and recommend against having your macro compiled away to nothing in release builds.

Share on FacebookTweet about this on TwitterShare on Google+Share on LinkedInEmail this to someonePrint this page

Comments are closed.

Comments are closed