8 tips for squashing bugs using ASSERT in C
One of the greatest bug-squashing tools available to embedded software developers is the assertion macro in C. Despite the power of ASSERT, though, I rarely see it implemented and in the cases where it is used the implementation is either flawed or incorrect. The following 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 ways 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:
Figure 1 – Function PreconditionThe 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:
Figure 2 – Assertion on function preconditionIn 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 immediately see where the code went wrong rather than some time much later.
Tip #3 – Use ASSERT to validate function post-conditions
Assertions can also be used to validate assumptions about the outputs of a function in a design by contract environment. For example, if the previously defined System_StateSet function returned 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:
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.
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 so that if the file doesn’t exist the error handler creates it with some default usable data for operations that occur further down code.
Tip #5 – 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 assertions 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 of turning assertions off is worth being taken.
An alternative is to leave the assertions enabled and redirect their output to a system log. This will ensure that any lingering bugs are easily identified but avoids halting the system, which may not be advisable.
Tip #6 – 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. But if the expression passed to ASSERT has side effects, that is, it changes the state of the embedded system, then disabling assertions will change the system's behavior. Developers should make sure that their expressions do not have side effects else they risk adding a sleeping time bug into their system that will only wake-up for production code.
Tip #7 – Assertions should be one to three percent 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 one, three or five percent, use assertions to your advantage and make developing embedded software a little more enjoyable.
Tip #8 – 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.
Assertions are an amazing tool which too many embedded software developers ignore. The seven tips explored in this article are just the tip of the iceberg 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.
Developers can also learn more by taking my five-day free course Embedded System Design Techniques: Writing Portable and Robust Firmware in C, starting August 31 at the Continuing Education Center of EDN's sister publication Design News. The course consists of one-hour audio lecture and text Q&A sessions streamed live at 2pm EDT each day, with assertions the focus of day four. The lectures and presentation materials will also be archived afterwards for offline availability.
Jacob Beningo is a Certified Software Development Professional (CSDP) whose expertise is in embedded software. He works with companies to decrease costs and time to market while maintaining a quality and robust product. Feel free to contact him at firstname.lastname@example.org, at his website www.beningo.com, and sign-up for his monthly Embedded Bytes Newsletter here.