Using assert() for Less Buggy Code
Tags: c, cpp
Published: 2024-01-28 17:17:11

---
title: Using assert() for Less Buggy Code
published: true
description: Using assert() in C & C++ for less buggy code.
tags: #c #cpp
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2024-01-27 13:35 +0000
---

## Introduction

The [`assert()`](https://en.cppreference.com/w/c/error/assert) macro, part of the C standard library defined in [`assert.h`](https://en.wikipedia.org/wiki/Assert.h), has been around since the early days of C as a mechanism to aid in writing less buggy code. It allows you to “assert” that some condition _must_ be true in order to continue. If the condition is _not_ met, the program will print an error message (including the assertion that was not met and the source file and line of the `assert()`), calls [`abort()`](https://en.cppreference.com/w/c/program/abort) that terminates the program, and typically provides a [core dump](https://en.wikipedia.org/wiki/Core_dump) as a debugging aid. Conditions include [preconditions](https://en.wikipedia.org/wiki/Precondition), [postconditions](https://en.wikipedia.org/wiki/Postcondition), and [invariants](https://en.wikipedia.org/wiki/Invariant_(computer_science)) for implementing [design by contract](https://en.wikipedia.org/wiki/Design_by_contract).

## Examples

Assertions are used most to check that function arguments are valid (preconditions), for example:

```c
void c_ast_op_params_min_max( c_ast_t const *ast,
                              unsigned *params_min,
                              unsigned *params_max ) {
  assert( ast != NULL );
  assert( ast->kind == K_OPERATOR );
  assert( params_min != NULL );
  assert( params_max != NULL );
  // ...
```

checks that no parameter is `NULL` and the “kind” of `ast` is `K_OPERATOR`.

Assertions are also good to check a pointer for `NULL` before assigning to it (a precondition), for example:

```c
c_ast_t *const param_ast = param_node->data;
assert( param_ast->param_of_ast == NULL );
param_ast->param_of_ast = func_ast;
```

ensures that you’re not overwriting a non-`NULL` pointer causing a [memory leak](https://en.wikipedia.org/wiki/Memory_leak) of the object to which the pointer already points.

Assertions can also be used to check invariants, for example:

```c
c_ast_t const* c_ast_untypedef( c_ast_t const *ast ) {
  for (;;) {
    assert( ast != NULL );
    if ( ast->kind != K_TYPEDEF )
      return ast;
    ast = ast->tdef.for_ast;
  }
}
```

not only checks that `ast` is not `NULL` initially, but that _every_ `for_ast` pointer in the linked list is not `NULL`.

> In case you’re wondering, these code snippets are from [cdecl](https://github.com/paul-j-lucas/cdecl).

## Sample Implementation

To demystify `assert()`, here’s a basic implementation (spread over several lines for clarity):

```c
// assert.h
#ifndef NDEBUG
#define assert(EXPR) (                        \
  (EXPR) ? (void)0 : (                        \
    printf("%s:%d: failed assertion '%s'\n",  \
      __FILE__, __LINE__, #EXPR               \
    ),                                        \
    abort()                                   \
  ))
#else
#define assert(EXPR)  ((void)0)
#endif /* NDEBUG */
```

The first thing to notice is that `assert()` is defined only if `NDEBUG` is _not_ defined.  The consequence is that if you `#include <assert.h>` in your program and do nothing else, assertions will be _enabled_, that is they will actually assert what you are asserting.

However, if you define `NDEBUG` (the value doesn’t matter), then `assert()` is defined to a do-nothing expression and assertions will be _disabled_ (“No Debug”), that is they will _not_ do anything at all. You _might_ want to do this for production code — more later.

The `assert()` macro itself is fairly straightforward: if the assertion expression evaluates to non-zero (true), it does nothing; otherwise it prints an error message and calls `abort()`.

> In order to call two statements in an expression, the [comma operator](https://en.cppreference.com/w/c/language/operator_other#Comma_operator) is used.

While this sample implementation uses a macro, _any_ implementation invariably will since it needs to obtain the file and line number of the `assert()` as well as the “stringification” of the expression — things only the preprocessor can give. While some implementations use a function to do the `printf()` and `abort()` instead, they call a function _only_ if the check fails; the check itself needs to be fast via inlining.

## Assertions vs. Errors & Exceptions

Errors, exceptions, and assertions are related in that they’re all used to catch and report an invalid state; but they’re for different purposes:

+ Errors are for “conceivable” invalid states, but there may be a way to recover and continue.
+ Exceptions are for errors that are “exceptional” (in the “unlikely” sense, not in the “outstanding” sense), but there still may be a way to recover and continue.

In C++ that has exceptions, the line between errors and exceptions can sometimes get blurry. For example, if you attempt to open a file that doesn’t exist, should that be an error or exception? It’s debatable. (Fortunately, this article is about assertions, so that debate can be deferred to another time.)

Assertions, however, are _only_ for an invalid state that should “never” happen ([inconceivable!](https://princessbride.fandom.com/wiki/Vizzini)) — but if it does anyway, it means there’s a bug and either:

1. While there may be a way to recover and continue, it’s better to crash and produce a core dump as an aid to debug and fix the bug; or:
2. There’s _no_ way to recover, so you have no choice but to crash.

Case 2 typically happens in a function that’s at the bottom of the call stack and there’s no way to report the error, to return the error all the way up the call stack, or, even if you could, the caller wouldn’t know how to handle the error.

In such a case, it’s better to crash in a controlled way rather than have the program limp along possibly doing irreparable damage, e.g., overwriting data in a file with garbage, before possibly crashing anyway and possibly far from where the assertion was not met making debugging harder.

## Disabling Assertions in Production Code

As mentioned, if `NDEBUG` is defined, then assertions are _disabled_ — and you _might_ want to do this for production code.

The arguments for disabling assertions include:

1. They take a small, but non-zero amount of time to perform the checks that can add up, especially in [hot code](https://www.hackterms.com/hot%20code).
2. Assertion failures are typically caught by unit tests.
3. Having your program crash generally results in unhappy customers.
4. An attacker has found a way to put your program into an invalid state failing an assertion and causing it to crash that can result in a [denial-of-service attack](https://en.wikipedia.org/wiki/Denial-of-service_attack).

The arguments against disabling assertions include:

1. Since the time to perform the checks is small, it’s likely small enough to be negligible. The way to know for sure is to [profile](https://en.wikipedia.org/wiki/Profiling_(computer_programming)) your program. If your `assert()` checks are inconsequential, then you can leave them enabled.
2. While assertion failures are typically caught by unit tests, passing a test suite can never _prove_ that no bugs exist, so better to leave assertions enabled to detect invalid states.
3. Having your program not crash but yield incorrect results (that customers may not even realize are incorrect) or cause lasting damage also generally results in unhappy customers and can be even worse for your reputation.
4. You should _never_ use assertions to validate any input anyway — more later.

Personally, I think assertions should be left enabled.  If you want to disable assertions _only_ in hot code, you can use `assert()` there and define `NDEBUG`, but then define your own variant macro similar to the sample implementation presented earlier that is unaffected by `NDEBUG`:

```c
#define always_assert(EXPR)  /* ... */
```

and use it everywhere else.  You could go even farther and define _three_ macros:

```c
#define precondition(EXPR)   /* ... */
#define invariant(EXPR)      /* ... */
#define postcondition(EXPR)  /* ... */
```

Then you could, say, disable only `invariant()` (since it’s more likely to be used in loops) and `postcondition()` yet leave `precondition()` enabled.

Still: disabling _any_ assertions means you’re running a higher risk of either uncaught bugs or damage.

## Best Practices

There are a few best practices for using assertions.

### No Side-Effects

Assertions _must not_ cause side-effects.

```c
assert( --count > 0 );         // No!
```

> Or, if they do, there _should_ be a comment that the side-effect is OK.

Why not? Because if assertions are disabled, the statement doesn’t happen at all.  In some cases, you might need to add a variable just to assert on:

```c
bool const colors_parsed = colors_parse();
assert( colors_parsed );
```

Doing so, however, can cause “unused variable” warnings when assertions are disabled. To fix that, add the [`maybe_unused`](https://en.cppreference.com/w/c/language/attributes/maybe_unused) attribute:

```c
// C23 or C++17
[[maybe_unused]] bool const colors_parsed = colors_parse();
assert( colors_parsed );
```

If you’re using C before C23 or C++ before C++17, you can instead simply cast the variable to `void`:

```c
// C < C23 or C++ < C++17
bool const colors_parsed = colors_parse();
assert( colors_parsed );
(void)colors_parsed;
```

### No Input Validation

Assertions _must not_ be used to validate any input, either from a human (e.g., via keyboard), machine (e.g., via socket), or file — even trusted humans, machines, or files. Your program should not crash because a human made a typo or you received or read corrupted or otherwise unexpected data.

### No `&&` for Unrelated Conditions

Assertions _should not_ use `&&` to conjoin _unrelated_ conditions:

```c
assert( n > 0 && n < N_MAX );  // OK
assert( n > 0 && p != NULL );  // Meh
```

Why not? Because for the second `assert()`, you won’t know which assertion failed: was `n` &le; 0 or `p == NULL`? Separate assertions should be used instead:

```c
assert( n > 0 );               // Better
assert( p != NULL );
```

## Adding a Message to `assert()`

Sometimes, you might want to include an additional string in the error message produced by `assert()`. Unfortunately the `assert()` macro can not optionally take a string to add to the error message, but there is a commonly used trick to work around this:

```c
assert( ("message goes here", ok) );
```

That is put the string before a comma operator: the string literal will be discarded (in terms of the value of the expression, but still included in the error message) and the result will be the value of `ok`.

> The extra `()` are necessary so what’s between them is passed as a single argument to `assert()` rather that two arguments separated by a comma.

While that trick works, some compilers will give you a “left operand of comma operator has no effect” warning. To silence that, you can cast it to `void`:

```c
assert( ((void)"message goes here", ok) );
```

At this point, however, employing the trick is a bit baroque. To fix that, you can simply define your own variant of the `assert()` macro:

```c
#define assert_msg(MSG,EXPR) \
  assert( ((void)(MSG ""), (EXPR)) )
```

and use that whenever you want an additional string included in the error message.

> The use of `""` will ensure that `MSG` is a string literal (which it must be) rather than a `char*` variable since the preprocessor will concatenate two adjacent string literals but anything else will be an error.

## Conclusion

Following a few simple best-practices, use of the `assert()` macro is a good way to help produce less buggy code.