Assertions, Static Asserts, and Debug vs Release Verification
Error Handling and Exception Safety

9.5 Assertions, Static Asserts, and Debug vs Release Verification

Not all errors are equal. Systems programmers distinguish between environmental errors (like a missing file or bad network packet, which should be handled at runtime) and developer bugs (like passing a null pointer to a function that assumes it is valid). We use assertions to catch developer bugs with zero production cost.

Let's look at compile time assertions and runtime debug checks.

Compile Time Checks: `static_assert`

A static_assert is checked entirely by the compiler during compilation. If the condition is false, compilation fails. Because it runs during compilation, it has zero runtime overhead.

It is ideal for checking template parameters, struct sizes, and compiler configurations:

#include <type_traits>

// Guarantee at compile time that integers are exactly 4 bytes on this machine
static_assert(sizeof(int) == 4, "This codebase requires 4 byte integers!");

template <typename T>
void processType() {
    // Force template parameter to be a floating point type
    static_assert(std::is_floating_point_v<T>, "Template parameter must be float or double!");
}
Verifying parameters using static_assert.

Runtime Checks: The `assert` Macro

For sanity checks that cannot be verified at compile time (like checking if a dynamic function argument is null), we use the assert macro (defined in the <cassert> header).

If the condition inside assert is false, it prints an error message and aborts the application immediately. This helps you catch bugs early during development.

Compiling Out Debug Overhead for Production

Checking assertions inside loop cycles degrades performance. In systems programming, we compile assertions out of our production binaries.

When you define the macro NDEBUG (usually by passing the flag -DNDEBUG to your compiler), the preprocessor replaces all assert(...) statements with empty instructions. The assertions disappear entirely from your final binary, ensuring zero production overhead.

#include <cassert>

void computeSquareRoot(double x) {
    // Sanity check during development: verify number is not negative!
    // Completely removed in release builds when NDEBUG is defined!
    assert(x >= 0.0);
    // Perform calculations...
}
Compiling out runtime assertions.

* The Safety Inspector Metaphor: A debug assertion is like having a safety inspector stand next to you during machine testing. They yell and stop the machine the second you do something wrong. In production (release build), the inspector goes home. The machine runs at full speed with no inspector overhead, because the testing confirmed the logic was correct.

Finished reading this lesson?