Ten bug-killing rules for C

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

Rule #1 – Braces

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.
// 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 the presence of empty statements and single statements that are not surrounded by braces. Code constructs of this type are often associated with bugs when nearby code is changed or commented out. This type of bug 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 be modified (for example, char const * p_data),
  • To define fields in structs and unions that cannot be modified (such as in a struct overlay for memory-mapped I/O peripheral registers), and
  • As a strongly typed alternative to #define for numerical constants.

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

Rule #3 – Keyword “static”

The static keyword shall be used to declare all functions and variables that do not need to be visible outside of the module in which they are declared.

Reasoning: C’s static keyword has several meanings. At the module-level, global variables and functions declared static are protected from inadvertent access from other modules. Heavy-handed use of static in this way 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) by any interrupt service routine,
  • To declare a global variable accessible (by current use or scope) by two 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 volatile eliminates a whole class of difficult-to-detect bugs by preventing the compiler from making optimizations that would eliminate requested reads or writes to variables or registers that may be changed at any time by a parallel-running entity.2

Rule #5 – Comments

Comments shall neither be nested nor used to disable a block of code, even temporarily. To temporarily disable a block 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;

Reasoning: Nested comments and commented-out code both run the risk of allowing unexpected snippets of code to be compiled into the final executable.

Rule #6 – Fixed-width data types

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

Reasoning: The ISO C standard allows implementation-defined widths for char, short, int, long, and long long types, which leads to portability problems. Though the 1999 standard did not change this underlying issue, it did introduce the uniform type names shown in the table, which are defined in the new header file <stdint.h>. These are 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 used to 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 the underlying format of signed data (for example, 2’s complement) and leaves the effect of some bit-wise operators to be defined by the compiler author.

Rule #8 – Signed and unsigned integers

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.
// 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.
   // This incorrect path is actually
   // executed because -9 + 6 becomes
   // (0x100 - 9) + 6 = 253.

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 vs. inline functions

Parameterized macros shall not be used if an inline function can be written to accomplish the same task.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 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 – Comma operator

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

 // 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 a line of its own is low. By contrast, the risk that either the compiler or a maintainer will misunderstand your intentions is high.

Courtesy: Michael Barr


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s