This second article on enforceable coding standards for embedded systems adds a set of additional bug-killing rules for using certain C keywords and naming global variables.
In Bug-Killing Coding Standard Rules for Embedded C, I shared a set of 10 practical C coding standard rules for embedded software developers to reduce or eliminate bugs in their firmware. This article provides five more such rules. If you only want the new rules, please jump ahead to the heading More bug-killing coding rules for C. First, I need to respond to one of the forum threads posted after that first coding rules article was published on Embedded.com. This also gives me a chance to reiterate a key guiding principle behind the Barr Group's Embedded C Coding Standard.
Enforceable coding standards
On the subject of brace location, which I didn't even mention directly, a great discussion raged in the Embedded.com forum comparing the Allman brace style (which puts each brace on a line by itself) and the One True Brace Style (a.k.a., 1TBS, wherein the opening brace ends the prior line).
The forum discussion began with the bold assertion that "The Allman brace style should NEVER be used for languages that use braces for compound statements [and] a semicolon for terminating statements." I am quite comfortable with you adopting either style. However, one of the reasons given for 1TBS is something I cannot agree with. The discussion participant said that it's hard for human code reviewers to spot a mistake such as:
if (a == b); { /* intended body of if statement */ }
Though I agree it's possible for a human code reviewer to overlook this, the example precisely illustrates the importance of one of our guiding principles: "To be effective in keeping out bugs, coding standards must be enforceable. Wherever two or more competing rules would be similarly able to prevent bugs but only one of those rules can be enforced automatically, the more enforceable rule is recommended."
The above example is precisely the sort of bug we want our coding rules to prevent through automated testing. Rule #1 said:
Braces ({ }) shall always surround the blocks of code (also known as compound statements) following if, else, switch, while, do, and for keywords. Single statements and empty statements following these keywords shall also always be surrounded by braces. |
That is, an if (a == b); is broken every time by definition, ensuring that the static analysis warning is regarded as a rule violation by your team. This is true regardless of your choice for brace placement, so I won't share my personal preference here.
More bug-killing coding rules for C
Here are more examples of coding standard rules you can follow to reduce or eliminate certain types of firmware bugs.
Rule #11 - Keywords "short" and "long"
The keywords short and long shall not be used. |
Reasoning: The ANSI/ISO C standard contains a set of strict requirements plus mere guidelines for the authors of C compilers. Among the guidelines (also known as "implementation-defined" behaviors) are the precise width, in bits, of the char, short, int, and long data types. ISO C mandates that variables of type short must hold at least 16 bits and long at least 32 bits, across all platforms. Other than that, the only strict requirement is that sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long). There are times when you must use char (see Rule #12). In other places (e.g., loop counter declarations), it makes sense to allow the compiler to choose the best/fastest width using int. But in keeping with Rule #6 concerning adoption of the C99 fixed-width integer types/names, it is appropriate to ban use of the short and long types altogether. Like the semi-colon followed by left-brace sequence above, an automated tool can be used to enforce this rule.
Rule #12 - Keyword "char"
Use of the keyword char shall be restricted to the declaration of and operations concerning strings. |
Reasoning: Among the implementation-defined behaviors of C is the signed-ness of a char data type. One compiler may treat your char variables as unsigned, another as signed—and yet both are technically ISO C compliant! This introduces subtle and potentially hidden risks related to using the bit-wise operators on signed char data (Rule #7) or mixing signed and unsigned data (Rule #8). The risk of bugs derived from this subtlety of C are entirely eliminated by choosing int8_t or uint8_t explicitly whenever the data is other than part or all of a string.
Rule #13 - Initialization of variables
All variables shall be initialized before use. |
Reasoning: Too many C programmers assume the C run-time will watch out for them. This is a very bad assumption, which can prove dangerous in a real-time system. We may sometimes get lucky with C startup code that zeros all uninitialized data or the stack. But lucky is not acceptable in a medical device or any other safety-critical product. By making initialization before use a hard rule on your project, you elevate the warning a static-analysis tool can raise on this to an error that must be dealt with by the team.
Rule #14 - Global variable names
The names of all global variables shall begin with the letter 'g'. For example, g_zero_offset. |
Reasoning: Eschew them all you like, but global variables are a fact of life in some parts of embedded software. There are two specific risks associated with their use.
The first risk of global variables is that a race condition between two or more asynchronous entities (be they CPUs, peripherals, ISRs, tasks, or a combination) will corrupt the data stored there. To prevent this, exclusive access must be established programmatically, such as via use of interrupt disables or mutex acquisition. The second risk is that the compiler will optimize away one entity's read or write because it cannot see the other user during compilation. Both risks can be eliminated, but only if everyone on the team sees that they are global variables. By naming all globals in an obvious way, code reviews become that much more effective—all the way down to the individual line of code or function.
Rule #15 - Constants in comparisons
When evaluating the equality or inequality of a variable with a constant value, always place the constant value on the left side of the comparison operator. |
if (0 == x) { /* Do this only if x is zero. */ }
Reasoning: It is always desirable to detect possible typos and as many other bugs as possible at compile-time; run-time discovery may be dangerous to the user of the product and require significant effort to locate. By following this rule, the compiler can detect erroneous attempts to assign (i.e., = instead of ==) a new value to a constant.1
Try adding some or all of the fifteen practical embedded C coding rules from this article and the preceding "Bug-Killing Coding Standard Rules for Embedded C" to your coding standard . And also be sure to check out Barr Group's Embedded C Coding Standard book.
Related Barr Group Courses:
Firmware Defect Prevention for Safety-Critical Systems
Top 10 Ways to Design Safer Embedded Software
Best Practices for Designing Safe & Secure Embedded Systems
Best Practices for Designing Safe Embedded Systems
Best Practices for Designing Secure Embedded Devices
Debugging Embedded Software on the Target
For a full list of Barr Group courses, go to our Course Catalog.
Endnotes
1. But remember, floating-point values should never be compared to constants. A number like 0.1x10-15 is not strictly equal to zero. [back]