Skip to the content of the web site.

Assertions and other debugging tools

When developing code, you may want to put in extra checks to make sure your code is indeed running correctly, but when you prepare the final product, you may not want to waste time executing all of these checks.

There are two tools that you can use to help you in your code development. The first is the assert macro, and the second is using the pre-processor.


Warning

Do not use anything that must be in the final product; for example, validation of data the source of which is external to the program. For example, if your class requires that a double is positive, but this is checked only with an assertion, assertions can be turned off, leading to security issues. Everything in this page should be used to verify that your program is in correct state during execution.


assert(...)

#include <cassert>

Having included the C assert library, you can now put an assertion anywhere you want in your code:

#include <cassert>

// Ingore this for now
#undef NDEBUG

unsigned long factorial( unsigned long n );

unsigned long factorial( unsigned long n ) {
    assert( n <= 20 );

    unsigned long result{1};

    for ( unsigned long k{2}; k <= n; ++k ) {
        result *= k;
    }

    return result;
}

The reason for the assertion is that $21! = 51090942171709440000$, and this is a number larger than what can be stored in an unsigned long. You may not want to have to unnecessarily check if the factorial function is being called with n > 20, but during development, you may never-the-less want to check just to make sure. If the factorial function is ever called with an argument greater than 20, the assertion will terminate the program, indicating where the assertion failed.

Suppose it now comes time to compile your code for the actual purpose you wrote it for. The extra check should never fail, and checking it would be a waste of time and effort. Consequently, you can now change one line:

#include <cassert>

// define the symbol NDEBUG (standing for 'No DEBUGging')
#define NDEBUG

unsigned long factorial( unsigned long n );

unsigned long factorial( unsigned long n ) {
    assert( n <= 20 );

    unsigned long result{1};

    for ( unsigned long k{2}; k <= n; ++k ) {
        result *= k;
    }

    return result;
}

The previous incantation, #undef says undo any definition of the NDEBUG symbol. It is already undefined, so this really doesn't change anything; however, if the NDEBUG symbol is defined, then not only does the assertion not check, it is completely stripped from the code when it is being compiled—it will compile as if you had deleted every line containing an assertion.

Other places you may want to include an assertion are in cascading conditional statements:

    if ( x <= -5.0 ) {
        // Do something...
    } else if ( x <= 0.0 ) {
        // Do something...
    } else if ( x <= 5.0 ) {
        // Do something...
    } else {
        assert( x > 5.0 );
        // Do something...
    }

Looking at the previous conditions, of course it should be true that $x > 5$; however, another programmer may accidentally make a change to the conditions, in which case the assertion may later fail.

Alternatively, suppose you wrote a simple function to compute the gcd of two numbers, but you didn't want to always check that the larger argument came first, and instead require that the programmer calling the function ensures that the larger argument comes first.

unsigned long gcd( unsigned long m, unsigned long n ) {
    assert( m >= n );

    while ( n != 0 ) {
        unsigned long r{m % n};
        m = n;
        n = r;
    }

    return m;
}

Similarly, suppose you wrote a very fast approximation of the sine function, but this could only approximate the value of sine for $0 \le x \le \pi$. For example, the following function has the minimum relative error for approximating the sine function using a cubic:

double fast_sin( double x ) {
    return ((
        -0.12982726700166469*x - 0.031041616418863258
    )*x + 1.0034924244212799)*x;
}

If you are checking each time this function is called to ensure that the argument passed falls within the required interval, this would significantly slow this function down. Instead, you could use:

// This line is necessary to ensure 'M_PI_2' (pi/2) is defined
#define _USE_MATH_DEFINES
#include <cmath>

#undef NDEBUG

double fast_sin( double x );

double fast_sin( double x ) {
    assert( (0.0 <= x) && (x <= M_PI_2) );

    return ((
        -0.12982726700166469*x - 0.031041616418863258
    )*x + 1.0034924244212799)*x;
}

Now, if you switch the #undef to #define, the check on the parameter value will not be made.


#ifndef

It is possible to add additional code in your source code that is only conditionally compiled, just like assert(...). This can be done by wrapping that code inside the following:

#undef NDEBUG

double minimum_spanning_tree( ... );

double minimum_spanning_tree( ... ) {
   return minimum_weight{0.0};
   // Do something to calculate the minimum spanning tree

   #ifndef NDEBUG
       for ( int k{0}; k < num_vertices; ++k ) {
           std::clog << k << "\t" << visited[k] << "\t" << distance[k] << std::endl;
       }
   #endif

   // Continue doing something to calculate the minimum spanning tree

   return minimum_weight;
}

The loop inside the #ifndef...#endif pre-processor directives will be compiled and executed only so long as the symbol NDEBUG is not defined (as #ifndef stands for "if not defined"). During your debugging phase, the symbol can be left undefined, and each time you execute this algorithm, the data will be printed to the log file and the log can be checked to ensure the algorithm is functioning correctly.

When, however, it comes time to compile the code for production purposes, by changing the #undef NDEBUG to #define NDEBUG, not only will the for loop not be executed, but it will not even be compiled—the compiler will not even see this code, you could even have a syntax error and the compiler will not make a peep.


assert in Python

After writing this, the author discovered that Python, too, has an assert statement, which is almost identical:

	assert condition

or

	assert condition, "Some text to be displayed"

If the condition fails, termination halts; and in the second case, the string is displayed on the console.