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:
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.
* 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.