A nice complement (and a generalization) of the MISRA C guidelines are the NASA/JPL C guidelines [1], which are also a very beautiful read. They can be succinctly summarized as ten simple rules [2] :
1. Avoid complex flow constructs, such as goto and recursion.
2. All loops must have fixed bounds. This prevents runaway code.
3. Avoid heap memory allocation.
4. Restrict functions to a single printed page.
5. Use a minimum of two runtime assertions per function.
6. Restrict the scope of data to the smallest possible.
7. Check the return value of all non-void functions, or cast to void to indicate the return value is useless.
8. Use the preprocessor sparingly.
9. Limit pointer use to a single dereference, and do not use function pointers.
10. Compile with all possible warnings active; all warnings should then be addressed before release of the software.
A lot of these are pretty similar to the restrictions built into the IEC 61131-3 'Structured Text' programming language (https://en.wikipedia.org/wiki/Structured_text). It's basically just removing things that commonly cause problems.
"Expressive power" is a two-edged sword, usually coming with some significant side effects in terms of your ability to reason about your code under varying circumstances. In the embedded world it's often more important to know strict bounds on processing time and memory usage. And when you're dealing with safety critical systems, it's critical to be able to reason explicitly about every aspect of your code.
In the PLC / industrial automation world there's the added consideration that your code will probably need to be maintained (or at least navigated and understood) by people with limited software skills. And given that systems tend to be "wide, not deep" (as in, hundreds or thousands of mostly-independent inputs that only interact via very simple logic), usually ladder logic or function block diagrams are the preferred way to implement these sorts of systems. Even Structured Text is considered a bit too technical by most people.
These rules are made for a context where you need to avoid fanciness. You want to do the bare minimum that gets the task done and gets it done with extraordinary reliability and robustness.
Fancy language constructs and complex algorithms are generally not helping. Keeping the program simple helps verification using automatic tooling and manual code reviews because the risk of hidden side effects or undiscovered defective code paths is minimized.
You can fly rockets without heap and function pointers, and that is definitely non-trivial.
More precisely, however, heap allocation is usually accepted during the initialization phase (the moment when you can still abort the process with no consequence if needed, i.e in the case of a rocket, before it takes off).
It's certainly useful for the code to be expressive. But sometimes it's more important for the code to be easy to analyze and easy to verify than for it to be expressive and/or concise. I know that the mantra goes that "concise code is easier to analyze" but that's not always the case. It's true that "concise" code is usually easier to "match against" an algorithm -- i.e. it's easier to read the code and figure out if it does what it has to do. But you also have to reason about whether what the runtime does behind the scenes is correct or not, and your reasoning has to exhaust every scenario. If the runtime offers no such (or very few) guarantees, "expressive" code is very hard to deal with.
These guidelines aren't meant to be applied for "general-purpose" code, which may be used by a human operator in a million possible ways. It's usually very special-purpose code that does only a handful of things (and if it doesn't, that's meant to be treated more like a design problem than anything else). Consequently, it's not that hard to figure out how much memory you need from the very beginning, and not allocate any of it on the fly.
Allocating memory on the heap complicates analysis a lot. It introduces potential timing issues. You have to make sure that no matter how your program runs, it'll always be able to allocate the memory it needs -- or to design it so that it runs correctly (i.e. predictably and in the right manner) even when an attempt to allocate memory fails. You have to bring in pretty complicated third-party code, or spend a lot of time and money writing your own (writing a memory allocator is trivial, but writing a good, safe one isn't exactly a walk in the park).
As for function pointers... that's more or less the same story. You don't need them that often for this sort of code, and when you do, the trade-off is usually worth it.
(In very rare situations it's not, but IMHO coding standards do the right thing here. Judgement calls about these things are very much subject to ego and organization politics. It's always better to err on the side of having to do extra work under some circumstances).
These guidelines don't produce the most good-looking code but they do make it harder to shoot yourself in the foot.
Obviously, there is an argument to be made about alternative solutions, such as code that you cannot physically point at your foot, but that's a discussion for another time (and also way more complicated than that...)
The most inconvenient part about MISRA, for me, is the fact their stance is open source tools can't state or paraphrase the rules [1] for copyright reasons.
Which is why the writing of this blog post is so bizarre, telling you that Rule 9 is pointless but not saying what Rule 9 is.
Admittedly the document is fairly priced at £10 per user - but good luck getting a rule added into an open source project like clang analyzer when you can't tell them what the rule is supposed to do
cppcheck has been making an effort in this direction - giving you the unique opportunity to back a kickstarter [2] that can't say what it's doing.
Picking up one of these safety critical guidelines with the intent to raise your own quality standards as an engineer or team is certainly a great idea. There is a lot of thought put into these documents and it's likely there are at least a few things you haven't thought about or perhaps you discount too quickly.
That being said, this isn't a spec for good software. It's more of a spec for auditing software that has to work in a predictable way. The main consumers for this are, yes, software engineers and managers, but it's especially useful for framing quality assurance efforts.
It's entirely possible to check all the boxes while still cranking out swill. For instance, I'd be surprised if the Volkswagen software that famously assisted in cheating on emissions tests was properly MISRA reviewed since a lot of the guidelines have to do with making sure the code does exactly what is intended, no more or no less. A full review with someone who could read code would either require the reviewer to ask "this is the code that cheats on the test, right?" or to be oblivious as to the full purpose for the code.
I agree with this point. The MISRA standard is mainly there to discourage use of the parts of C that most often cause faults.
You can comply to MISRA C while ignoring some of the rules if you determine the risk to be justifiable.
Ideally, these un-safe methods would be removed from the C language, but then that would be removing the low level ability from C that it is very suited for. So these guidelines exist to note the most dangerous parts of C, but it is still up to the developer to determine how to use it safely.
Interesting point about MISRA and malicious rules hidden in the code. Something like the test stand discovery heuristic would be a state in a high level state machine somewhere if done properly (hopefully, this is an explicit state machine...). If it were, it would become obvious when trying to trace that state to requirements.
However, modern engine control units have such complex requirements that complete verification against all of them is very unlikely in practice, especially in a third party audit.
> Rule 9: This precludes the use of a construct that is not supported by the C standard. As such it is a pointless rule.
I'm not sure that's true. Compilers (let's say, MSVC++) tend to support a mismash of standards and de-facto standards. If you are forbidden from using a construct not supported by the C standard (under which you are building) you are in fact specifying strict compliance with a particular standard, which is meaningful.
MISRA and other such standards are great places to start thinking about reliability and safety. That said, I think there is a danger in believing that code which complies with a certain set of standards is safe. Attempting to bring non-safety code into compliance with MISRA may increase safety, but safety, like security, is something that needs to be designed in, both to the software and the overall system. Standards compliance is often just a CYA move to avoid legal and financial liability. Often, trying to make a codebase compliant with a standard like MISRA will reduce overall code quality as good software design practices take a backseat to compliance metrics and QA reports.
While many items in MISRA are sensible (some I do find quite counterproductive, if code quality in terms of readability and maintainability is also a concern), I have seen MISRA standards being applied to a legacy embedded codebase with terrible effects. What was being done, for example, was putting explicit typecasts everywhere a warning popped up respective mismatched integer sizes or similar, instead of refactoring the code to improve variable typing.
The best end result was largely unreadable code. Why? Because the junior developers tasked with the MISRA compliance efforts were measured by reducing the number of warnings instead of improving code quality.
The bonus was actual breakage in the field, as the test coverage of the legacy code being converted was particularly abysmal. But not to worry, another junior dev was tasked with creating unit tests for every function! With a codebase unsuitable to unit testing through fuzzy interfaces, that effort was about as effective as one would expect, too.
</rant>
This is precisely why the industry is adopting ISO 26262 safety standards which then require that all safety critical code be MISRA compliant, but not necessarily that non-safety critical code be compliant, as long as safety critical code is isolated from the non-safety critical portions.
I think a lot of misra rules are bullshit and not account for improvements in computer science.
- goto is useful for error handling and when you have to double break
- function pointers are useful and easy to read state machines and also for functional programming, which is sometimes easier to comprehend and produces better patterns
- "Restrict functions to a single printed page." in my experience does not work. I would rather partition functions by topic or by level of "zoom"/abstraction
- "Use a minimum of two runtime assertions per function." that doesn't really say anything useful imho. Use as much as experience tells you too.
Yes goto in C is perfectly safe. Far as I've been able to discern 1970's computer scientists believed they could prove things about program iff they restricted the language features. Problem is, you basically can't and goto in structured languages isn't an impediment. See also the rules against using continue and multiple returns.
Personally I can't see how function pointers are worse than languages pushing return addresses onto the same fucking stack they push data.
Restrict functions to a single page sounds like a home work assignment rule that's been cargo culted into a standard. Functions should do one logical thing and nothing more or LESS.
The two runtime assertions might make sense for a space craft where cosmic rays are randomly flipping bits. Don't make much sense otherwise. My theory is functions that modify state should barf hard when told to do something stupid. Functions that don't should return an error.
That's fine. You don't have to use every rule, you just have to express why you're choosing not to use them. That's why they're separated into categories.
I was thinking recently about a related problem: suppose all I care about is feeding my C compiler with code which is entirely free of undefined behaviour. How would I do that?
MISRA C doesn't get me there - it helps, but isn't enough to guarantee the absence of undefined behaviour, despite its 'rule' to avoid UB [0] (which is not mechanically verifiable). It would presumably be possible to use a Turing-complete subset of C, defined such that compliance to it could be verified automatically. I imagine such a subset might be awfully claustrophobic, but my intuition is that this should be possible.
Another approach would be to write code in a different language entirely - either one defined to be free of undefined-behaviour, or one where the absence of it can practically be established statically/automatically. We could then transpile to C code. The transpiler could then guarantee never to generate C code with undefined behaviour.
It seems to be possible to do this today by writing Ada SPARK code and using the (proprietary, terribly pricey) CCG transpiler. [1] (SPARK is a fine choice here. When programming in the SPARK subset, it's impossible to invoke Ada's equivalent of undefined behaviour [0].)
Is there a 'tidier' answer to this problem, or perhaps something obvious that I'm missing?
A related question: is there a 'tidy' way that I can be certain my C code is insensitive to platform-specific behaviours (what C calls unspecified), such as the evaluation-order of argument expressions?
> Such issues are not important in machine generated code (because such code is never read by humans).
A does not imply B. There's often broken generated code that is read by humans. Readability of generated code can be helpful and often essential, in finding bugs.
Yep, sometimes we have to do without cushy debuggers and easy reproducibility in a test environment. A registers dump and a top few stack items may very well be all you have for figuring out a bug.
I found this a while ago, after reading the guidelines and wanting to hear the opinions of others on it. Interestingly, the reason I found out about them to begin with was George Hotz during an interview going something like "I thought MISRA guidelines were stupid but then I actually read them and like, these are by really smart people, man!"
I just pulled it up and here's what he said in the interview ad verbatim:
"All of this code is Open Source, readable, and I believe now it's all MISRA C-compliant, too."
Interviewer cuts in: "What's that mean?"
"Um, MISRA is like the automotive coding standard. At first I—you know, I've come to respect—I've been reading, like, the standards lately, and I've come to respect them, they're actually written by very smart people!"
[The interviewer says something irrelevant to the point here that I don't feel like typing up and that Hotz didn't seem to agree with and that I don't really agree with.]
"MISRA's written by, like, computer scientists. And you can tell by the language they use—you can tell by the language they use, they talk about like whether certain, uh, conditions in MISRA are decidable or undecidable, and I and I and I...you mean like the halting problem? And, yes! Alright, you've earned my respect, I will read carefully what you have to say, and we want to make our code compliant with that."
He obviously worded his praise a bit strangely, but a standard written well enough to tame someone like George Hotz is fairly alluring. I was probably as surprised as he was when I found out how good they were. They set the bar so high that very little matches it as far as technical writing goes.
> This goes far beyond coding guidelines. It reads more like THINKING guidelines
Well, yes. That's the "safety culture" influence, the idea that there is not really such a thing as an "accident". With careful thought all the possible outcomes should be forseeable, and mitigation should be in place for all risks. This includes what might be called affordances for the programmer - tools that are prone to misuse and mistakes should be avoided. Guardrails around thought.
This chafes if you got into programming for the freedom, but is a lot more reassuring for people driving the car.
(edit: my own personal benchmark for technical writing is the dully-named "App Note 47: High Speed Amplifier Techniques" by the legendary Jim Williams. From its own preface:
"This publication represents the largest LTC commitment to an application note to date. No other application note absorbed as much effort, took so long or cost so much. ... We intend to supply useful high speed products and the level of support necessary for their successful application(such high minded community spirit is, of course, capitalism’s deputy)."
It is 132 pages including photographs, illustrations, screenshots of osciliscopes, equations, and a few jokes.)
Safety critical systems aren't places that tolerate clever hacks for the sake of being clever. But meeting application requirements while staying within the safety guidelines can be an interesting challenge in its own right.
Thanks for the link. Looking forward to reading it. I first learned about Jim Williams when I bought a book called Analog Circuit Design from the EDN press. He edited most of the book, but it is a collection of beliefs, values and attitudes around Analog Design by like-minded folks. It's equal parts educational and humorous.
That was the original motivation but it slowly mutated into general guidelines for safety-critical C in embedded systems.
Although not an official regulatory requirement it is often used as a guideline in low-resource targets for Medical embedded systems.
In aerospace for example the JPL guidelines are also influenced by the initial MISRA consortium effort. And at least in style and motivation the JSF for C++ ia also influenced by MISRA.
1. Avoid complex flow constructs, such as goto and recursion.
2. All loops must have fixed bounds. This prevents runaway code.
3. Avoid heap memory allocation.
4. Restrict functions to a single printed page.
5. Use a minimum of two runtime assertions per function.
6. Restrict the scope of data to the smallest possible.
7. Check the return value of all non-void functions, or cast to void to indicate the return value is useless.
8. Use the preprocessor sparingly.
9. Limit pointer use to a single dereference, and do not use function pointers.
10. Compile with all possible warnings active; all warnings should then be addressed before release of the software.
[1] https://yurichev.com/mirrors/C/JPL_Coding_Standard_C.pdf
[2] https://en.wikipedia.org/wiki/The_Power_of_10:_Rules_for_Dev...