Tutorial: Achieve reliable embedded code with MISRA C
No software engineering process can guarantee reliable code, but following the right coding guidelines can dramatically reduce the number of errors in your code.
A valuable tool in achieving this goal is MISRA C, a subset of the C language defined by 141 rules constraining the language. The rules are well thought out and provide many insights into likely errors and constructs that cause non-portable behavior. Almost anyone who writes C code will find MISRA’s coding guidelines useful. Consistent use of MISRA C is likely to increase software reliability.
MISRA stands for Motor Industry Software Reliability Association. The MISRA consortium publishes a document that defines MISRA C rules: Guidelines for the Use of the C Language In Critical Systems. For more information on MISRA visit www.misra.org.uk.
According to the MISRA C document, its purpose is not to promote the use of C in embedded systems. Rather, the guidelines accept that C is being used for an increasing number of projects. The MISRA C guidelines discuss general problems in software engineering and note that C does not have as much error checking as other languages do. Thus the guidelines hope to make C safer to use, although they do not endorse MISRA C or C over other languages.
MISRA C is based on the ISO/IEC 9899:1990 C standard, which is identical to the ANSI X3.159-1989 standard, often called C ’89. Thus every MISRA C program is a valid C program. The original standard consisted of only 127 rules. In the time since the original publication, there have been some questions and concerns about some of the rules which motivated the second edition of the standard (MISRA 2004). It contains a number of clarifications and improvements to the original rules. Also, a few troublesome rules have been removed altogether.
What is MISRA C?
MISRA C is written for safety critical systems, and it is intended to be used within a rigorous software development process. The standard briefly discusses issues of software engineering, such as proper training, coding styles, tool selection, testing methodology, and verification procedures.
MISRA C also talks about the ways to ensure compliance with all of the rules. Some of the rules can be verified by a static checking tool or a compiler. Many of the rules are straightforward, but others may not be or may require whole-program analysis to verify. Management needs to determine whether any of his tools can automatically verify that a given rule is being followed. If not, this rule must be checked my some kind of manual code review process. Where it is necessary to deviate from the rules, project management must give some form of consent by following a documented deviation procedure. Other non-mandatory “advisory” rules do not need to be followed so strictly, but cannot just be ignored altogether.
The MISRA rules are not meant to define a precise language. In fact, most of the rules are stated informally. Furthermore, it is not always clear if a static checking tool should warn too much or too little when enforcing some of the rules. The project management must decide how far to go in cases like this. Perhaps a less strict form of checking that warns too little will be used throughout most of the development, until later when a stricter checking tool will be applied. At that point, somebody could manually determine which instances of the diagnostic are potential problems.
Most of the rules have some amount of supporting text that justifies the rules or perhaps gives an example of how the rule could be violated. Many of the rules reference a source, such as parts of the C standard that state that such behavior is undefined or unspecified.
Taxonomy of the Rules
The MISRA C rules are classified according to the C construct that they restrict. However, most of the rules fall into a couple of groups.
The first group of rules consists of rules that intend to make the language more portable. For example, the language does not specify the exact size of the built in data types or how conversions between pointer and integer are handled. An example of this first group is:
Rule 6.3 (advisory): Typedefs that indicate size and signedness should be used in place of the basic types.
This rule effectively tries to avoid portability problems caused by the implementation-defined sizes of the basic types. We will return to this rule in the next section.
A second set of rules deal with undefined behaviors which have an impact on code portability. A program with an undefined behavior might behave logically, or it could abort unexpectedly. For example, using one compiler, a divide by 0 might always return 0. However, another compiler may generate code that will cause hardware to throw an exception in this case. Many of the MISRA C rules are there to forbid behaviors that produce undefined results because a program that depends on undefined behaviors behaving predictably may not run at all if recompiled with another compiler.
Unlike this first group of rules that guard against portability problems, the second group of rules intends to avoid errors due to programmer confusion. While such rules don’t make the code any more portable, they can make the code a lot easier to understand and much less error prone. An example of this second category is:
Rule 7.1 (required): Octal constants (other than zero) and octal escape sequences shall not be used.
By definition, every compiler should do octal constants the same way, but as I will explain later, octal constants almost always cause confusion and are rarely useful.
A few other rules are geared toward making code safe for the embedded world. These rules are more controversial, but adherence to them can avoid problems that many programmers would rather sweep under the carpet.