C and Functional Safety in the automotive industry, part 3: Mind your languageby Bogdan Andone
If developers occasionally feel hamstrung by MISRA’s restrictions, those (thankfully) rare incidents of catastrophic embedded software failure in the news put this level of circumspection back into context.
In the previous posts we took a look at the rise of C as the dominant language for safety-critical embedded systems, and then examined some of the pitfalls programmers can fall into when using such a powerful tool, and the need to be unusually self-reliant in terms of anticipating safety concerns in compiling and runtime environments.
In this post, we’ll look at further institutions and practices which focus on a C subset’s clarity and readability, and the challenges that this aspect of programming contains.
Avoiding language constructs that are prone to human errors
Many software bugs appear because the code is written in an unstructured way, using a poor or non-scalable design. In such cases, only a couple of incremental changes can transform the project into ungovernable spaghetti code.
Another source of potential issues is the more or less obfuscated code sometimes enjoyed by geek programmers; things like packing several expressions, statements and calls onto a single line for the sake of maximizing code visible on screen, or making the code hard to understand by others. Obviously, these are aspects that have to be avoided in developing functional safe software.
The rule of thumb here is to maximize transparency and make the code easy to read, understand and review by others. In this respect, there are several powerful constructs provided by C which should be avoided as much as possible. Preprocessor directives, pointer arithmetic and typecasting are just a few of them.
MISRA C standardized a very detailed set of rules to help developers reduce the risks of using error-prone coding patterns. One essential tool in regards to code clarity is the project’s coding guidelines. If MISRA C is an official standard shared by different industries, the coding guidelines are an organization/project-specific document intended to address stylistic or conventional aspects that won’t necessarily be related to the language subset, but which will help to cohere the team’s efforts and expectations for how the code is to be structured and documented. Such guidelines will usually include naming conventions, code file structure, coding layout and even comment documentation formats.
Returning to MISRA C, it’s notable that this industry-oriented branch of the C programming language enforces strong guidelines and restrictions intended to minimize error-prone coding patterns.
Strong typing is not really enforced in C, but is one of the best practices in safety-critical systems, in order to avoid issues caused by loss of value, loss of sign or loss of precision. Therefore MISRA C provides a number of rules for moving C closer to a more strictly-typed methodology.
Firstly it decrees that implicit type casting be avoided in order to prevent accidental and undesirable casts; next, that explicit type casts be limited to cases where the entire information of the value is preserved (for example, permitting integers and floats to be converted only to wider types, whilst preserving the original signs of any affected integers).
MISRA C also rules that function-like preprocessing macros be avoided, since, among other reasons, they do not allow parameter type checking. Also forbidden is unions usage, because of the risk that data may be misinterpreted.
Limited usage of pointers
Pointers are one of the key features in C, and hard to avoid entirely in any real-life application. In the context of C, ISO 26262 casts constraints for enforcing strong typing and also stipulates that pointer to integer conversions (and the other way around) are forbidden. Further, additions and subtractions are the only mathematics that pointers are permitted to undertake during operations which involve array indexing.
Another nice feature of C, macros, are considered on ‘the dark side’ with respect to safety-critical systems. The possibility to define, redefine and undefine them at any moment can easily lead to confusion with respect to the existence or meaning of a macro.
Enabling or disabling code blocks by preprocessing macro directives can also lead to code which is hard to read, opening the gates to dead code or strange control flows.
Function-like macros also have to be avoided for the reasons previously mentioned in regards to strong typing. Fortunately, in this case, the inline functions represent a much better alternative.
Though coding layout is outside the scope of MISRA C (which defers to project or organization specific coding guidelines, as mentioned above), it nonetheless has a couple of rules addressing the structure of some standard C constructs.
Examples of rules in this category include:
- if constructs shall always be followed by a compound statement (i.e. a statement block in braces)
- switch constructs should always have a default clause
- for loop counters and control variables must fulfill a set of rules regarding where and how they are initialized, modified or checked
- Parentheses should be used for emphasizing operator priority in expressions
Data flow consistency enforcement
This aspect is generally derived from the design and regards those few features in C that support concepts such as data encapsulation and modularity. Best practice advises developers to use unique identifiers in order to avoid ambiguity about the meaning of a variable, to minimize the visibility of a variable to the relevant scope only, and to use const for read-only variables.
Though constraints related to control flow are not specific to C, two basic rules also considered to be best practice are: not to use unbounded recursions, since they represent a high risk of generating stack overflows; and to ensure that a function concludes in only single possible exit point. Though the latter guideline is intended to promote better readability, it has since come under question for a tendency to lead to deeper if-block trees, which in themselves make code harder to read.
That’s it for this look at the pre-eminent place of C (and its subset) over nearly fifty years in the embedded software sector. Opinions might differ as to whether its dominance in the field is due to a luxurious legacy infrastructure from decades of development, or because it’s still the best and most powerful tool for the job after all these years in the face of so many challenges. But in an increasingly locked-down software development world, C still represents the power that accompanies responsibility.
ISO26262- Part 6