More Bug-Killing Coding Standards for Embedded C

来源:互联网 发布:淘宝的量子恒道在哪里 编辑:程序博客网 时间:2024/04/30 21:42

This next installment of enforceable coding standards forembedded systems offers bug-killing rules for using certain C keywords andnaming global variables.

In "Bug-Killing Coding Standard Rules for Embedded C,"I shared a set of 10 practical C coding standard rules for embeddedsoftware developers to reduce or eliminate bugs in their firmware. Thisarticle provides five more such rules. If you only want thenew rules, please jump ahead to the headingMore bug-killing coding rules for C.First, I need to respond to one of theforum threadsposted after that first coding rules article was published on Embedded.com. This also gives me a chance to reiteratea key guiding principle behind theNetrino Embedded C Coding Standard.

Enforceable coding standards

On the subject of brace location, whichI didn't even mention directly, a great discussionraged in the Embedded.com forum comparing the Allman bracestyle (which puts eachbrace 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 bracestyle should NEVER be used for languages that use braces for compoundstatements [and] a semicolon for terminating statements." I am quitecomfortable with you adopting either style. However, one of the reasonsgiven for 1TBS is something I cannot agree with. The discussionparticipant said that it's hard for human code reviewers to spot amistake such as:

 


if (a == b);
{
/* intended body of if statement */
}

Though I agree it's possible for a human code reviewer to overlookthis, the example precisely illustrates the importance of one of ourguiding principles: "To be effective in keeping out bugs, codingstandards must be enforceable. Wherever two or more competing ruleswould be similarly able to prevent bugs but only one of those rules canbe enforced automatically, the more enforceable rule is recommended."

The above example is precisely the sort of bug we want our coding rulesto prevent through automated testing. Rule #1 said:

 

Braces ({ }) shall alwayssurround the blocks of code (also known as compound statements) followingif, else, switch,while, do, and forkeywords. Single statements and empty statements following these keywordsshall 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 violationby 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 reduceor 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 setof strict requirements plus mere guidelines for the authors of Ccompilers. Among the guidelines (also known as "implementation-defined"behaviors) are the precise width, in bits, of the char,short, int, and longdata types. ISO C mandates that variables of type shortmust hold at least 16 bits and long at least 32 bits,across all platforms. Other than that, the only strict requirement is thatsizeof(char) <= sizeof(short) <=sizeof(int) <= sizeof(long). Thereare times when you must use char (see Rule #12). In otherplaces (e.g., loop counter declarations), it makes sense to allow thecompiler to choose the best/fastest width using int. Butin keeping with Rule #6 concerning adoption of the C99 fixed-width integertypes/names, it is appropriate to ban use of the shortand long types altogether. Like the semi-colon followedby left-brace sequence above, an automated tool can be used to enforcethis rule.

Rule #12 - Keyword "char"

 

Use of the keyword char shall be restricted to thedeclaration of and operations concerning strings.

Reasoning: Among the implementation-defined behaviors ofC is the signed-ness of a char data type. One compilermay treat your char variables as unsigned, anotheras signed—and yet both are technically ISO C compliant! Thisintroduces subtle and potentially hidden risks related to using thebit-wise operators on signed char data (Rule #7) or mixing signed andunsigned data (Rule #8). The risk of bugs derived from this subtletyof C are entirely eliminated by choosing int8_t oruint8_t explicitly whenever the data is other than partor 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 willwatch out for them. This is a very bad assumption, which can provedangerous in a real-time system. We may sometimes get lucky with Cstartup code that zeros all uninitialized data or the stack. But luckyis not acceptable in a medical device or any other safety-criticalproduct. By making initialization before use a hard rule on yourproject, you elevate the warning a static-analysis tool can raise onthis 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'. Forexample, g_zero_offset.

Reasoning: Eschew them all you like, but global variablesare a fact of life in some parts of embedded software. There are twospecific risks associated with their use.

The first risk of global variables is that a race condition betweentwo or more asynchronous entities (be they CPUs, peripherals, ISRs,tasks, or a combination) will corrupt the data stored there. To preventthis, exclusive access must be established programmatically, such asvia use of interrupt disables or mutex acquisition. The second risk isthat the compiler will optimize away one entity's read or write becauseit cannot see the other user during compilation. Both risks can beeliminated, but only if everyone on the team sees that they are globalvariables. By naming all globals in an obvious way, code reviews becomethat much more effective—all the way down to the individual line ofcode or function.

Rule #15 - Constants in comparisons

 

When evaluating the equality or inequality of avariable with a constant value, always place the constant value on theleft side of the comparison operator.

 


if (0 == x)
{
/* Do this only if x is zero. */
}

Reasoning: It is always desirable to detect possibletypos and as many other bugs as possible at compile-time; run-timediscovery may be dangerous to the user of the product and requiresignificant effort to locate. By following this rule, the compiler candetect erroneous attempts to assign (i.e., = insteadof ==) a new value to a constant.1

Try adding some or all of the fifteen practical embedded C coding rulesfrom this article and the preceding "Bug-Killing Coding Standard Rules for Embedded C"to your coding standard . Then follow my blog at embeddedgurus.net/barr-code for more tips to keep bugsout of your embedded system.

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.