assert()

The assert.h header file defines the assert() macro, which is used to verify that a program meets certain conditions at runtime. If a condition is not met, the program will terminate with an error message. This macro is often referred to as an “assertion.”

For example, consider the following code:

1
assert(PI > 3);

When the program reaches this line, it checks whether the variable PI is greater than 3. If it is, the program continues to run; if not, the program will terminate and provide an error message.

The assert() macro takes an expression as its parameter. If the expression evaluates to true (a non-zero value), assert() has no effect, and the program continues. If the expression evaluates to false (zero), assert() will trigger an error, writing a message to the standard error stream (stderr). This message includes the failed expression, the file name, and the line number where the assertion failed. Finally, it calls the abort() function to terminate the program (the prototype for abort() is found in the stdlib.h header file).

For instance:

1
2
z = x * x - y * y;
assert(z >= 0);

This assertion is equivalent to the following code:

1
2
3
4
if (z < 0) {
puts("z less than 0");
abort();
}

If the assertion fails, the program will output an error message like this:

1
Assertion failed: (z >= 0), function main, file /Users/assert.c, line 14.

The format of the error message is as follows:

1
Assertion failed: [expression], function [function_name], file [file_name], line [line_number].

Here, the sections in brackets are replaced with actual data.

Using assert() has several advantages: it automatically identifies the file and line number where the issue occurred, and it allows you to enable or disable assertions without changing the code. If you have confirmed that your program is functioning correctly and no assertions are needed, you can define a macro NDEBUG before including assert.h:

1
2
#define NDEBUG
#include <assert.h>

After recompiling the program, the compiler will disable all assert() statements in the file. If issues arise again, you can remove this #define NDEBUG directive (or comment it out) and recompile to re-enable the assertions.

However, one drawback of assert() is that it introduces additional checks, which can increase the runtime of the program.

static_assert()

Introduced in C11, static_assert() is used for compile-time assertions. Its syntax is as follows:

1
static_assert(constant-expression, string-literal);

static_assert() takes two parameters: the first, constant-expression, must be a compile-time constant; the second, string-literal, is an error message. If the first parameter evaluates to false, a compilation error occurs, and the second parameter is displayed as the error message.

For example:

1
static_assert(sizeof(int) == 4, "64-bit code generation is not supported.");

This code means that if the size of int on the current machine is not 4 bytes, a compilation error will be raised.

It’s important to note that static_assert() only runs during compilation and cannot evaluate variable values. If you try to assert on a variable, it will result in a compilation error:

1
2
3
4
int positive(const int n) {
static_assert(n > 0, "value must be > 0");
return 0;
}

This code will cause a compilation error because the compiler cannot determine the value of n at compile time.

The benefits of static_assert() include detecting errors during compilation, which can save development time by avoiding runtime errors. Additionally, assertions within functions may not be executed if the function isn’t called, whereas static_assert() checks regardless of whether the function runs. Lastly, static_assert() does not generate executable code, meaning it does not add any runtime performance overhead.