Barr Group FacebookBarr Group TwitterBarr Group LinkedInBarr Group Vimeo

 Printable PDF

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: 

Embedded Software Boot Camp

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

Embedded Security Boot Camp

Best Practices for Designing Secure Embedded Devices

Debugging Embedded Software on the Target

Developing Effective Coding Standards

For a full list of Barr Group courses, go to our Course Catalog. See our Training Calendar for our latest public training calendar.

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]

Comments:

I like to use BYTE, WORD, and DWORD for my 8-bit, 16-bit, and 32-bit variables. This doesn't preclude being able to use "int" for counters that I want to leave up to the compiler to chose. When porting to a new platform, it becomes a change in one spot in the types.h file that is included globally, and the datatype is readily apparent from the declaration.

The problem with your approach is that you like BYTE, WORD, and DWORD. And the next guy likes U8, U16, and U32. Then there's the vendor that prefers INT8U, INT16U, and INT32U. And the other vendor that prefers UINT1, UINT2, and UINT4. And UINT8 could mean either 8 bits or 8 bytes depending on which of them you're talking to. And someone else prefers all lowercase letters in any of those possibilities.

C99 introduced standard names for these and you should use those.

Apart from being non-standard, as already noted, these are poor names as they give no explicit indication of either the signedness or the size of the type! An 8-bit processor has a Word-Size of 8; a 16-bit processor has a Word-Size of 16; a 32-bit processor has a Word-Size of 32, etc... Hence "WORD" and "DWORD" are, themselves, implementation-defined - which is precisely what we're trying to avoid!

Andrew Neil
Antronics Ltd.

Most of your rules are good, and I agree with them all (in spirit, if not in detail). Except Rule 15, which is unnecessary; the problem it attempts to avoid is much better solved using a static analysis tool such as lint. Additionally, in many cases, neither operand of a conditional expression is a constant.

Thanks for sharing this post. This is a very helpful and informative material.

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.

Good post and keep it up.

What’s happening and how it’s done. Get in the know.

Sign up for our newsletter today!

Receive free how-to articles, industry news, and the latest info on Barr Group webinars and training courses via email. 

To prevent automated spam submissions leave this field empty.