• Rule #8:
Signed integers shall not be combined with unsigned integers in comparisons or expressions. In support of this, decimal constants meant to be unsigned should be declared with a 'u' at the end.
Example (don't):
uint8_t a = 6u;
int8_t b = -9;
if (a + b < 4)
{
// This correct path should be executed
// if -9 + 6 were -3 < 4, as anticipated.
}
else
{
// This incorrect path is actually
// executed because -9 + 6 becomes
// (0xFF – 9) + 6 = 252.
}
Reasoning: Several details of the manipulation of binary data within signed integer containers are implementation-defined behaviors of the C standard. Additionally, the results of mixing signed and unsigned data can lead to data-dependent bugs.
• Rule #9:
Parameterized macros shall not be used if an inline function can be written to accomplish the same task.3
Example:
// Don't do this ...
#define MAX(A, B) ((A) > (B) ? (A) : (B))
// ... if you can do this instead.
inline int max(int a, int b)
Reasoning: There are a lot of risks associated with the use of preprocessor #defines, and many of them relate to the creation of parameterized macros. The extensive use of parentheses (as shown in the example) is important, but doesn't eliminate the unintended double increment possibility of a call such as MAX(i++, j++). Other risks of macro misuse include comparison of signed and unsigned data or any test of floating-point data.
• Rule #10:
The comma (,) operator shall not be used within variable declarations.
Example (don't):
char * x, y; // did you want y to be a
// pointer or not?
Reasoning: The cost of placing each declaration on a line of its own is low. By contrast, the risk that either the compiler or a maintainer will misunderstand your intentions is high.
Add the above rules to your coding standard to keep bugs out of your firmware. Then join me at Embedded.com next month for the next installment of the Barr Code column and to learn more techniques for bug-proofing embedded systems.
Michael Barr is the author of two books and over 40 articles about embedded systems design, as well as a former editor in chief of this magazine. Michael is also a popular speaker at the Embedded Systems Conference and the founder of embedded systems consultancy Netrino. You may reach him at mbarr@netrino.com or read more by him at www.embeddedgurus.net/barr-code.
Endnotes:
1. Complex variable declarations like this can be difficult to comprehend. However, the practice shown here of making the declaration read "right-to-left" simplifies the translation into English. Here, "p_timer is a constant pointer to a volatile timer_t" register set. That is, the address of the timer registers is fixed while the contents of those registers may change at any time.
2. Anecdotal evidence suggests that programmers unfamiliar with the volatile keyword think their compiler's optimization feature is more broken than helpful and disable optimization. I suspect, based on experience consulting with numerous companies, that the vast majority of embedded systems contain bugs waiting to happen due to a shortage of volatile keywords. These kinds of bugs often exhibit themselves as "glitches" or only after changes are made to a "proven" code base.
3. The C++ keyword inline was added to the C standard in the 1999 ISO update.