“You will see a few warnings when you compile the code; you can safely ignore those,” the engineer claimed confidently. He had contacted me the previous day stating that he had found a bug in our Embedded Workbench compiler, and just sent me his entire project rather than a simple example to replicate the supposed issue.
When I compiled the project on my machine, “a few warnings” was actually 402 warnings, to be more precise. After sifting through the warnings, I found one that was the source of the problem. The warning message for this line of code stated, “Undefined behavior: Order of volatile accesses not guaranteed”. What the warning message was trying to tell both of us is that the way the engineer wrote the code was getting into a dark corner of the C language specification that actually wasn’t defined. So, when the arguments to a function call are volatile accesses to variables or are nested function calls themselves, the compiler can choose the order in which the different accesses are performed.
To wring the most performance out of the code, the compiler may choose to not go simply left-to-right in the accesses, but rather reorder them if it can make the code tighter and/or faster, depending on the optimization selected by the user. The way that the engineer wrote the code depended on left-to-right accesses for the code to function properly. I showed him how to rewrite the code so that all compilers would interpret it the same way—and more importantly—it would function the way he expected.
Beware Bug Bites
This situation reinforced a maxim that I’ve used in my professional career: Warnings are bugs just waiting to bite you. Therefore, you should always attempt to clean up every warning in your code by understanding why the warning was issued and what it’s really trying to tell you. With this in hand, you’re better equipped to make the appropriate changes in your application so that your code is more robust.
However, compilers and linkers typically don’t examine your code in much detail. Consequently, simply having zero warnings when you build isn’t enough to engender confidence in the defect level of your code. Rather, it’s a first step to making your application easier to debug. A more thorough analysis of the code is necessary to avoid common pitfalls that developers fall into as well as to avoid some of the undefined behaviors of the C and C++ language that were mentioned previously. In other words, you need to get serious about code quality.
Developers commonly use two approaches to get high levels of code quality: coding standards and static-analysis tools. Employing both of these in conjunction with testing will not only make your code as defect-free as possible, but it can actually foreshorten time-to-market by allowing you to meet your application’s release criteria much more quickly.
“The later you find a bug, the more expensive it is to fix.” We have all heard this platitude so many times that we never question its veracity. But have you ever stopped to wonder why this is so? To reveal this, let’s take a look at a typical mean-time-to-failure (MTTF) curve (see figure).
Not much test time is needed to find defects at the beginning of a project, but it usually takes quite a bit of testing to find a defect later in the development lifecycle.
As you can see, when you first start testing an application it’s easy to find quite a few defects. As the software matures, it takes more and more testing time to find the next defect. The reason that it’s more expensive to fix a bug later in the test cycle is that it artificially skews your curve, making it take even more test time to meet your company’s release criteria. Therefore, the earlier you find the defect in testing, then the less impact it has on the project schedule and is “cheaper” to fix.
But what if you could foreshorten the amount of testing time by reducing the number of defects in your code? That’s where code quality can help you. By enforcing coding standards and using static analysis, you can eliminate defects while desk-checking your code. Once you check code into a build, then every defect counts against your release metrics. However, if you find it before then, it’s like the bug never even happened!
Now that we know why code quality is important, let’s look at the two common ways to achieve this. The first way is by implementing a coding standard. Many organizations have coding standards that set forth things like how variables and functions are named, calling conventions, how comments are structured, etc. Some go so far as to limit the actual size of each individual piece of source. The idea is to increase readability during code walkthroughs to make it easier to find defects and to improve maintainability.
These are all great goals and should be part of a standard, but there are standards that go beyond this level. One of the most popular is MISRA C. MISRA stands for the Motor Industry Software Reliability Association, which—as the name implies—was born out of the automotive industry to promote safe and reliable embedded coding practices. However, many other industries such as avionics, medical, industrial controls, and even consumer products have co-opted these rules for their usage. That’s because the standard tries to remove all of the undefined behaviors in C as well as prevent users from engaging in “risky” programming practices that are error-prone. For example, look at this code snippet:
What’s wrong with it? You probably don’t see the subtle behavior, and that’s the issue that MISRA C is trying to avoid. The issue with this statement is that the decrement of c only happens if a equals b. While it may be that you fully understand that behavior and it’s what you intended, you’re betting that anyone who reviews or maintains your code understands that as well. It’s easy to miss and easy to get wrong, which is why MISRA C won’t allow this kind of behavior—it can easily add to your bug tally. Here’s another example:
a == 3 || b > 5
What has precedence? It’s hard to remember, which is why MISRA C forces logical ANDs and ORs to be primary expressions by forcing you to wrap them in parentheses like:
(a == 3) || (b > 5)
It’s clearer and easier to read. Parentheses are free in code—use them liberally! Depending on which flavor of MISRA C you use (1998, 2004, or 2012), there are up to 226 different rules like this that help you avoid common mistakes in your code and therefore reduce the number of defects found during formal testing, therefore it drastically reduces the amount of debugging your application requires. There is even a MISRA C++ to help you code safely in C++!
The second approach to improving code quality is to do static analysis of your application. Most static-analysis tools can check your code for MISRA C/C++, but the better tools go beyond that. For example, the C-STAT static-analysis tool in IAR Embedded Workbench adds a few hundred other rules that are pulled from a variety of other sources, like the Common Weakness Enumeration (CWE) from Mitre.
The CWE is a list of some of the most common sources of defects in application code compiled by exhaustive research from the folks at Mitre. For instance, they recommend against doing comparisons of floats with == or != operators or using a divisor without having a check for non-zero. More sophisticated static-analysis tools like C-STAT can also find paths through the code where you’re dereferencing a NULL pointer—and show you the path through the code that makes that pointer be NULL when the dereference occurs. They can do the same thing for uninitialized variables, too.
In addition, the CWE guards against misusing a pointer by directly accessing a member of a struct by using an offset from the struct. If you change the struct, it can easily cause a litany of bugs in your code, all of which will delay your project’s release. These are just a small sample of the defects that static analysis can help you find. Removing these issues improves your code quality, and thus reduces the amount of required debugging.
So, what happened to the engineer I mentioned at the beginning of the article? I told him about my maxim of warnings-as-bugs and advised him to spend some time cleaning them up to improve his code quality. He said he would and thanked me for my help. About a week later, he ran into another issue and asked for help. He said he would send me a new code dump and then surprised me by stating that he had cleaned up all of the warnings! I was very pleasantly surprised until I received the new code dump and realized that he had chosen to suppress the warning diagnostics during the build. Some people never learn, but I like to think that we can all be taught new tricks.
If you want to spend less time at the end of your project debugging, then spend a little more time at the beginning of your project using coding standards and static analysis to help you improve your code quality. As Ben Franklin opined famously, “An ounce of prevention is worth a pound of cure.”
Shawn Prestridge is Senior Field Applications Engineer at IAR Systems.