Bug-Killing Coding Standard Rules for Embedded C(zz)

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

A C coding standard can help keep bugs out of embedded software byleveraging common language features and development tools.

A coding standard defines aset of rules for programmers to follow in a given language. This makesa coding standard similar in purpose to the English standard known asStrunk & White(The Elements of Style).The original versionof this popular book of grammar and other rules for written English wasonce described as a "forty-three-page summation of the case forcleanliness, accuracy, and brevity in the use of English."

Embedded software is also better when rules for "cleanliness, accuracy,and brevity" are followed. The adoption of a coding standard by a teamor a company has many benefits. For example, a coding standardincreases the readability and portability of software, so that firmwaremay be maintained and reused at lower cost. A coding standard alsobenefits a team of software developers and their entire organization byreducing the time required by individual team members to understand orreview the work of peers.

In my view, though, one of the biggest potential benefits of a codingstandard has been too long overlooked: a coding standard can help keepbugs out. It's cheaper and easier to prevent a bug from creeping intocode than it is to find and kill it after it has entered. Thus, a keystrategy for keeping the cost of firmware development down is to writecode in which the compiler, linker, or a static-analysis tool can keepbugs out automatically—in other words, before the code is allowed toexecute.

Of course, there are many sources of bugs in software programs. Theoriginal programmer creates some of the bugs, a few lurking in the darkshadows only to emerge months or years later. Additional bugs resultfrom misunderstandings by those who later maintain, extend, port,and/or reuse the code.

The number and severity of bugs introduced by the original programmercan be reduced through disciplined conformance with certain codingpractices, such as the placement of constants on the left side of eachequivalence (==) test.

The original programmer can also influence the number and severity ofbugs introduced by maintenance programmers. For example, appropriate useof portable fixed-width integer types (such as int32_t)ensures that no future port of the code to a new compiler or targetprocessor will encounter an unexpected overflow.

The number and severity of bugs introduced by maintenance programmerscan also be reduced through the disciplined use of consistentcommenting and stylistic practices, so that everyone in an organizationcan more easily understand the meaning and proper use of variables,functions, and modules.

Available C standards

Over the years, I have reviewedthe details of many coding standards, including several specificallyaimed at the use of C for firmware development. I have also studiedMISRA's 2004"Guidelines for the Use of the C Language in Safety-Critical Systems."The authors of "MISRA-C" are knowledgeable ofthe risks in safety-critical system design and their guidelines narrowthe C language to a safer subset.(For more information about the MISRA-C Guidelines, see Nigel Jones's article"MISRA-C Guidelines for Safety Critical Software".)

Unfortunately, although firmware coding standards and MISRA-C sometimesoverlap, coding standards too often focus primarily on stylisticpreferences—missing their chance to help reduce bugs. MISRA-C, bycontrast, offers great advice for eliminating bugs but very littleguidance on practical day-to-day issues of style.

It was thus out of necessity that my team of engineers developed theNetrino Embedded C Coding Standard.This coding standard was created from the ground up to help keep bugsout of firmware. In addition, we applied the following guidingprinciples, which served to eliminate conflict over items that aresometimes viewed by individual team members as personal stylisticpreferences:

  • Individual programmers do not own the software they write. Allsoftware development is work for hire for an employer or a client and,thus, the end product should be constructed in a workmanlike manner.
  • For better or worse (well, mostly worse), the ANSI/ISO "standard" Cprogramming language allows for a significant amount of variability inthe decisions made by compiler implementers. These many so-called"implementation-defined," "unspecified," and "undefined" behaviors,along with "locale-specific options" (see Appendix G of the standard),mean that programs compiled from identical C source code may behavevery differently at run time. These gray areas in the language greatlyreduce the portability of C programs that are not carefully crafted.
  • The reliability and portability of code are more important than eitherexecution efficiency or programmer convenience.
  • The MISRA-C guidelines were carefully crafted and are worthy ofstudy. On the small number of issues where we have a difference ofopinion with MISRA-C, we make this clear. Of course, followers ofNetrino's coding standard may wish to adopt the other rules of MISRA-Cin addition.
  • To be effective in keeping out bugs, coding standards must beenforceable. Wherever two or more competing rules would be similarlyable to prevent bugs but only one of those rules can be enforcedautomatically, the more enforceable rule is recommended.

Ten bug-killing rules for C

Here are some examples of coding rules you can follow to reduce oreliminate certain types of firmware bugs.

Rule #1 - Braces

 

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.

 


// Don't do this ...
if (timer.done)
// A single statement needs braces!
timer.control = TIMER_RESTART;

// Do this ...
while (!timer.done)
{
// Even an empty statement should be surrounded by braces.
}

Reasoning: Considerable risk is associated with thepresence of empty statements and single statements that are notsurrounded by braces. Code constructs of this type are often associatedwith bugs when nearby code is changed or commented out. This type ofbug is entirely eliminated by the consistent use of braces.

Rule #2 - Keyword "const"

 

The const keyword shall be used whenever possible, including:

 

  • To declare variables that should not be changed after initialization,
  • To define call-by-reference function parameters that should not bemodified (for example, char const * p_data),
  • To define fields in structs and unions that cannot be modified (suchas in a struct overlay for memory-mapped I/O peripheral registers), and
  • As a strongly typed alternative to #define for numericalconstants.

Reasoning: The upside of using constas much as possible is compiler-enforced protection from unintendedwrites to data that should be read-only.

Rule #3 - Keyword "static"

 

The statickeyword shall be used to declare all functions and variables that donot need to be visible outside of the module in which they are declared.

Reasoning: C's static keyword hasseveral meanings. At the module-level, global variables and functionsdeclared static are protected from inadvertent accessfrom other modules. Heavy-handed use of static in thisway thus decreases coupling and furthers encapsulation.

Rule #4 - Keyword "volatile"

 

The volatile keyword shall be used whenever appropriate,including:

  • To declare a global variable accessible (by current use or scope) byany interrupt service routine,
  • To declare a global variable accessible (by current use or scope) bytwo or more tasks, and
  • To declare a pointer to a memory-mapped I/O peripheral register set(for example, timer_t volatile * const p_timer).1

Reasoning: Proper use of volatileeliminates a whole class of difficult-to-detect bugs by preventing thecompiler from making optimizations that would eliminate requested readsor writes to variables or registers that may be changed at any time bya parallel-running entity.2

Rule #5 - Comments

 

Comments shall neither be nested nor usedto disable a block of code, even temporarily. To temporarily disable ablock of code, use the preprocessor's conditional compilation feature(for example, #if 0 ... #endif).

 


// Don't do this ...
/*
a = a + 1;
/* comment */
b = b + 1;
*/

// Do this ...
#if 0
a = a + 1;
/* comment */
b = b + 1;
#endif

Reasoning: Nested comments and commented-out code bothrun the risk of allowing unexpected snippets of code to be compiledinto the final executable.

Rule #6 - Fixed-width data types

 

Whenever the width, in bits or bytes, of an integer value mattersin the program, a fixed-width data type shall be used in place ofchar, short, int,long, or long long. The signed andunsigned fixed-width integer types shall be as shown in Table 1.

 

Reasoning: The ISO C standard allowsimplementation-defined widths for char,short, int, long,and long long types, which leads to portabilityproblems. Though the 1999 standard did not change this underlying issue,it did introduce the uniform type names shown in the table, which aredefined in the new header file <stdint.h>. Theseare the names to use even if you have to create the typedefs by hand.

Rule #7 - Bit-wise operators

 

None of the bit-wise operators (in other words, &,|, ~, ^,<<, and >>) shall be usedto manipulate signed integer data.

 


// Don't do this ...
int8_t signed_data = -4;
signed_data >>= 1; // not necessarily -2

Reasoning: The C standard does not specify theunderlying format of signed data (for example, 2's complement) andleaves the effect of some bit-wise operators to be defined by thecompiler author.

Rule #8 - Signed and unsigned integers

 

Signed integers shall not be combined with unsigned integers incomparisons or expressions. In support of this, decimal constants meantto be unsigned should be declared with a 'u' at the end.

 


// Don't do this ...
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
// (0x100 - 9) + 6 = 253.
}

Reasoning: Several details of the manipulation ofbinary data within signed integer containers are implementation-definedbehaviors of the C standard. Additionally, the results of mixing signedand unsigned data can lead to data-dependent bugs.

Rule #9 - Parameterized macros vs. inline functions

 

Parameterized macros shall not be used if an inline functioncan be written to accomplish the sametask.3

 


// 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 withthe use of preprocessor #defines, and many of themrelate to the creation of parameterized macros. The extensive useof parentheses (as shown in the example) is important, but doesn'teliminate the unintended double increment possibility of a call suchas MAX(i++, j++). Other risks of macro misuse includecomparison of signed and unsigned data or any test of floating-point data.

Rule #10 - Comma operator

 

The comma (,) operator shall not be used within variabledeclarations.

 


// Don't do this ...
char * x, y; // did you want y to be a pointer or not?

Reasoning: The cost of placing each declaration on aline of its own is low. By contrast, the risk that either the compileror a maintainer will misunderstand your intentions is high.

Add the above rules to your coding standard tokeep bugs out of your firmware. And read the follow-on article More Bug-Killing Coding Standard Rules for Embedded C. Then follow my blog at embeddedgurus.net/barr-code to learn more techniques for bug-proofing embedded systems.

Endnotes

1. Complex variable declarations like this can be difficultto comprehend. However, the practice shown here of making thedeclaration read "right-to-left" simplifies the translation intoEnglish. Here, "p_timer is a constant pointer to avolatile timer_t" register set. That is, the addressof the timer registers is fixed while the contents of those registersmay change at any time.[back]

2. Anecdotal evidence suggests that programmers unfamiliar with thevolatile keyword think their compiler's optimizationfeature is more broken than helpful and disable optimization. I suspect,based on experience consulting with numerous companies, that the vastmajority of embedded systems contain bugs waiting to happen due to ashortage of volatile keywords. These kinds of bugs oftenexhibit themselves as "glitches" or only after changes are made to a"proven" code base.[back]

3. The C++ keyword inline was added to the C standard inthe 1999 ISO update.

原创粉丝点击