7 Tips for using assertions in C

One of the greatest bug squashing tools available to embedded software developers is the assert macro. Despite the power of assert, I rarely see it implemented and in the cases where it is used the implementation is either flawed or incorrect. The following seven tips should help elucidate not only when and where to use assert but also how to begin to use it properly.

Tip #1 – Memorize the definition of an assertion

Assertions are a confusing topic to many developers because of the temptation to use assertions in a way for which they were not designed. The clearest definition of an assertion I have ever encountered states:

“An assertion is a Boolean expression at a specific point in a program that will be true unless there is a bug in the program.”

Developers examining the above definition of an assertion should note three critical points:

  • An assertion evaluates an expression as either true or false
  • The assertion is an assumption of the state of the system at a specific point in the code
  • The assertion is validating a system assumption that if not true reveals a bug in the code

Tip #2 – Use assert to validate function preconditions

Assertions work great in a design-by-contract environment where a developer has clearly defined preconditions for a function. The assertion can be used to check that the inputs to the function meet the precondition criteria. Take the following code snippet in Figure 1 as an example:

/* Precondition: State should be less than maximum */
void System_StateSet(SystemState_t State)
{
    SystemState = State;
}

Figure 1 – Function Precondition

The State input to the function should fall within the defined system states. If State is outside the valid states it isn’t an error but a bug! An assertion can be used to verify the assumption that the State is valid as seen in Figure 2 below:

/* Precondition: State should be less than maximum */
void System_StateSet(SystemState_t State)
{
    assert(State < SYSTEM_STATE_MAX);
    
    SystemState = State;
}

Figure 2 – Assertion on function precondition

In the event that State is not less than the maximum, the assertion expression will be evaluated as false and program execution will then cease. Stopping the program execution makes it very easy for a developer to see where the code went wrong immediately instead of some time much later.

Tip #3 – Use assert to validate function post-conditions

Assertions can also be used to validate the assumptions about the outputs of a function in a design by contract environment.  For example, if the previously defined System_StateSet function returned the the SystemState variable a developer would expect it to also be within the expected range.  An assertion could be used to monitor for a bug as follows in Figure 3:

/* Precondition: State should be less than maximum
 * Postcondition: SystemState */
SystemState_t System_StateSet(SystemState_t State)
{
    assert(State < SYSTEM_STATE_MAX);
    SystemState = State;
    
    assert(SystemState < SYSTEM_STATE_MAX);
    return SystemState;
}

Figure 3 – Assertion on function post condition

Examining the above code, a developer may feel that these checks are worthless.  How could SystemState after it was just set ever be greater than SYSTEM_STATE_MAX?  The answer is that it shouldn’t ever be which is why if somehow it does change, maybe through an interrupt or parallel thread, the assertion will immediately flag the bug.

Tip #4 – Don’t use assert for error handling

After having memorized the definition of an assertion a developer should have engrained in their mind that assertions are for detecting bugs NOT for error handling.  Error handling is software designed to respond to improper user inputs and unexpected sequences of events.  Errors are expected to happen in a system but just because there is an invalid input doesn’t mean that the code has a bug in it.  Error handling should be kept separate from bug hunting.  A classic example of an improper use of an assertion is to check the file pointer when attempting to open a file for reading.  An example is shown in Figure 4.

FileReader = fopen("UserData.cfg", 'r');
assert(FileReader != NULL);

Figure 4 – Improper use of an assertion

The reader can clearly see that the result of attempting to open the file is dependent on the state of the file system and user data and not at all related to a bug in the code.  Instead of an assertion, a developer should have written an error handler that if the file doesn’t exist it creates it will some default usable data for operations that occur further down code.

Tip #4 – assert is meant for development NOT production

The original intent of the assert macro is for it to be enabled during development and later disabled for production. Enabling and disabling assert is done using the macro NDEBUG. Properly implemented assertions should have nearly no impact on an embedded system when they are disabled. The problem is that if testing is performed with them on, which should be done to catch any bugs, turning them off now results in a product being shipped that is in a different state than the one that was tested.

Assertions do use up some code space but more importantly they take a few clock cycles to evaluate their Boolean expression. Bare metal systems with limited resources could have their execution timing drastically affected by turning off the assertions, resulting in new bugs in the production system. The development team needs to decide if the risk is worth being taken. An alternative is to leave the assertions enabled and redirect their output to a system log so that any lingering bugs are easily identified but halting the system may not be advisable.

Tip #5 – Don’t allow assertions to have side effects

The default implementation of assert will allow a developer to include executable code as part of the Boolean expression. For example, a state variable could be implemented as part of the expression passed to assert. If the expression passed to assert has side effects, that is, it changes the state of the embedded system, disabling assertions will change the behavior of the system. Developers should make sure that their expressions do not have side effects are they risk adding a sleeping time bug into their system that will only wake-up for production code.

Tip #6 – Assertions should be 1 – 3% of the code

Every developer has their own opinion on how many assertions should exist within a code base. The one number that can be agreed upon is that the percentage of assertions in a code base should be greater than zero. Assertions provide a great way for developers to discover bugs the moment they occur within a code base. Debugging is one of the biggest time wasters and discouraging components of developing an embedded system. Whether a developers’ number is 1, 3 or 5 percent, use assertions to your advantage and make developing embedded software a little more enjoyable. If anything, we know that have 0 percent is not the right solution!

Tip #7 – Use assertions as executable code comments

Assertions make for great comments!  A nicely written expression can tell a developer exactly what they should expect at a given point in the code.  Developers should architect their assertions to give a clearer understanding of what is happening in the system which in turn will help decrease bugs.

Conclusion

Assertions are an amazing tool which too many embedded software developers ignore. The 7 tips explored in this article is just the tip of the ice berg on how to use assertions properly. The next step readers can take is to setup and start using assertions on a test bench and investigate how they work in a real embedded system.

Share >

5 thoughts on “7 Tips for using assertions in C

  1. Nice article Jacob!
    One additional tip: Modify your assert handler function to break into debugger.
    For example, on arm add: __asm(“bkpt #0”);
    Have a great 2019,
    Best Regards, Dave

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.