Summary: Debugging is hard. We need to seed our code with constructs that find bugs automatically.
C compilers offer a very power debugging construct in the assert() macro. Yet it's quite rare to find the use of assertions in firmware.
First, a refresher: if assert's argument is TRUE, nothing happens. If FALSE, the macro's default behavior is to print out an error message which includes the source filename and line number. That's not particularly useful for many embedded systems, but it's trivial to recode assert to take some other action, like to log the error and stop, or flash an LED.
The macro is disabled when DEBUG is set FALSE, so in production assert() uses no resources.
Why use this construct? Assert causes the system to fail immediately when a fault is detected. Contrast that with the normal behavior of a C function: something goes wrong (e.g., a divide by zero, a pointer out of range) and the function returns, pushing garbage data up the call change. Function after function manipulates the results, turning one person's garbage into the mother of all landfills, until the system decides it's time to push a swimming pool of morphine into the patient's arm.
Failing immediately makes it much easier to track the cause of problems. The cause is generally right at hand, not the result of something that happened millions of cycles ago.
In "Assessing the Relationship between Software Assertions and Code Quality: An Empirical Investigation," the authors showed a stunning correlation between assertion density and quality code. "Investigating the Use of Analysis Contracts to Support Fault Isolation in Object Oriented Code" goes even further: it demonstrates that bugs caught by assertions are often orders of magnitude cheaper to find and fix than those detected by conventional debugging. Better code. Produced cheaper. All for no out-of-pocket costs.
Assert does burn some CPU cycles and memory, and so may not be appropriate in resource-constrained embedded systems, but with the proliferation of 32 bit MCUs most of these concerns go away.
Consider this snippet:
assert sqrt(b*b - 4*a*c)>=0; result= (-b + sqrt(b*b - 4*a*c))/(2*a);
That sure looks computationally expensive, but GCC evaluates the duplicated discriminant only once.
Some companies keep assertions active no matter how DEBUG is set, but define assert() to take different actions: immediate shutdown when DEBUG is TRUE, or send an email (or something similar) to the developers when the switch is FALSE.
The macro has a serious flaw, though. It will take any argument, even one with an assignment or a function call. The code will likely behave differently with different settings of DEBUG. One solution is to redefine assert as follows:
#define assert_e(a) ((void)(a), assert(a)) //lint -emacro(730, assert_e)
The second line is a lint directive for PC-lint (www.gimpel.com). Use assert_e() instead of assert(), and PC-lint will toss out errors whenever the assertion argument has a side effect.
Here's a case from just a few weeks ago where a sanity check, or an assertion, would have prevented this "we sure are some dumb programmers" message from appearing:
Or how about Sunday's weather:
What do you think? Do you routinely use assertions?
Published May 15, 2013