124 comments

[ 4.3 ms ] story [ 201 ms ] thread
The MISRA guidelines are awful.
Could you be more specific? Are they badly written, or are they bad at achieving the intended purpose? Or is the purpose itself a bad idea?

And, whichever answer you give to that, why are they bad at it?

They are called "misery C" by people in the industry for a reason. They don't help with the safety all that much, but make a lot of code just fucking ugly.
Exactly. It reduced C to, almost, BASIC. Reduces available design patterns. Reduces available language features.

HOWEVER: having a MISRA-C LINT which is required to run on all code that makes it into an automobile is, ultimately, a good thing. Yes, the homogenization is ugly, but it does mean that classes of errors just can't occur.

The concept is great. There is also MISRA C++ of which I know almost nothing; is that any better?

MISRA C++ lags quite a bit relative to C++'s evolution over the last 20 years. It's closer to JSF C++ style guide.
I only wrote a little MISRA-C code, but IIRC we had a process to get waivers which was fairly reasonable I think.

BASIC, but with a human-driven process that bumps you back to C when necessary, with the understanding that multiple people look at the evil C-like code, actually seems like a fine language.

They're called "misery C" because it's an easy joke.
(comment deleted)
(comment deleted)
From my experience, maintaining a standalone/embedded printf library - MISRA is a combination of two things: Common-sense rules, and pain-in-the-ass rules. The former are not very interesting and you probably have them in your head as coding guidelines anyway; but the latter, well, are a pain.

Example: Avoiding implementation-defined types like `int` _every_where, even in places where my code doesn't care about what sizeof(int) is; or when I want to store the result of a system call which returns an int.

I was able to accommodate most (?) of the MISRA rules (https://github.com/eyalroz/printf/issues/77), but mine is just a small library, so I don't know how restrictive they would be for a larger codebase. I suspect it would be painful and force you to avoid some legitimate and useful coding patterns.

> Avoiding implementation-defined types like `int` _every_where

Well that is the correct thing to do. I guess you can't really complain to MISRA about C being crazy.

> Well that is the correct thing to do.

Is it? I can think of quite a few cases where using 'int' is fine; why is that a problem?

Cross-platform code - Is your int still 16 bits on your new CPU? Of course you know* your variable is always between 0 and 15, but the MISRA checker might not**.

Today we have int16_t et al., so this is much less of an issue.

*) IME "know" is often merely a "believe".

**) Not sure that even if you have a MISRA checker that has a suitable inference, it is allowed to discard alarms on this kind of violation. I wrote MISRA test cases for something for a suitable powerful checker/analyzer, but I can't remember this detail anymore.

Why do you think that believing that sizeof(int) == 4 is worse than believing that int32_t exists? It doesn't have to
If a type doesn't exist in your toolchain, your compile will fail an you will know why.

Conversely, incorrect assumptions about size/size/capacity can lead to subtle run-time bugs.

(comment deleted)
You have to check sizeof(int) somewhere. Why not do that in a header? To make sure you're using the checked int, define a new type, eg "int4" in that header, too, and use that. Congratulations, you'll fulfill this MISRA rule :)

Or just use int32_t and let the compiler fail if your Toolchain does not have that. Then just write an appropriate header to add it (with the correct checks) ;)

If you know your variable is between 0 and 15 and care deeply about this, I kinda think you should get explicitly-ranged integer types added to your language rather than use a type that's defined for -32767 - 32767 but doesn't enforce it.

Also, IME it obscures bugs in your code if you're forced to overspecify things, because now someone reading it has to determine if all those explicitly specified details are actually correct or not.

Definitely! But MISRA is about writing robust code with an error-prone language, not picking a different language that is better designed.

Your second point doesn't make any sense. Someone reading `int` has to determine if that is actually correct or not too.

> Your second point doesn't make any sense. Someone reading `int` has to determine if that is actually correct or not too.

If you assign an int32_t to an int16_t because you know the actual range of values fits at that point, later readers don't necessarily know that. (And it can change.)

If they're both int, there's no possible problem.

You're suggesting using the same integer type everywhere. If so you can use `uint64_t`.

To put it another way:

> If they're both int32_t, there's no possible problem.

I agree, and there is tooling to do this kind of thing with C/C++. If this was embedded into the language, you'll quickly learn that static checking has its limits; dynamic checking introduces a new class of failure mode into your embedded, safety critical (as in kills or destroys) system, which might also not be what you want. It's a conundrum :(

The second statement contradicts the first - int16_t is too specific and hides errors, but an arbitrary range is not?

> The second statement contradicts the first - int16_t is too specific and hides errors, but an arbitrary range is not?

The arbitrary range is actually capable of being correct. int16_t is almost never "correct", since it's larger than the actual range of values.

It can also be said to be an over-optimization, since on some platforms like x86 it's easier to deal with 32-bit values than 16-bit ones.

The compiler takes care of this kind of optimization via static analysis. And who said x86? You're right, but what about Power9 register windows vs the three-and-a-half brain cells of x86?

I'm not in the mood of discussing this or convincing you otherwise. If you made up your mind to use int over intWhatever_t then so be it. We're not working on the same project and I suppose you're not building anything that might kill me.

I can think of lots of cases where carelessly using int might cause issues. And none at all where it's actually better than using a different type. Aside from anything else, it's probably better to default to unsigned types.

I'm fully willing to believe that MISRA-C contains a lot of silly, unnecessary rules, but IMO this isn't one of them. If typing out uint32_t, etc is too long, then you can always create an alias.

> it's probably better to default to unsigned types.

Security teams at our company have told me this too but I absolutely disagree. If you're going to write in C, and you don't want your variable to overflow, then you should use a type that doesn't have defined overflow semantics.

The reason for this is that it makes it easier to find bugs, because you want to discover wrong behavior rather than just define it to do an arbitrary wrong thing. You can do this with UBSan.

In fact, as long as it's okay for your code to crash, I endorse shipping it with UBSan turned on. (Presumably this isn't the case for a car MCU though. Don't ask me what to do there.)

In practice the arbitrary sizing of basic C integer types (int, long, etc.) has proven to be extremely error prone. Basically it's virtually impossible to use them correctly (except in small examples). You don't get any portability benefits because it's too difficult to write code that is actually portable across word sizes, and you get extra bugs when things don't behave like you expect. There's basically no upside.

It's much less error prone to use fixed size types, especially since approximately all computers are now 32 or 64 bit.

I once lost hours on an Arduino project exactly because of this. I was using the SD card library, and there was an infinite loop bug. Turned out to be because most Arduinos (at the time) are 16 bit, but I was using a 32 bit one. The code had been written using `int` but also assumed that int was 16 bits!

The code was less portable because of the "portable" int sizing system. It didn't work at all with other sizes, whereas if it had been written using uint16_t it would have worked fine.

You can say "well they made a mistake", but remember we're talking about MISRA. Avoiding mistakes is the whole point.

> I can think of quite a few cases where using 'int' is fine; why is that a problem?

It's a problem when your toolchain or target changes, and assumptions you were making (explicitly or implicitly) about 'int' now fail in difficult to understand ways.

Lots of replies kind of missed my point (my fault, probably) - I think there are cases where 'int' is the correct type to use, and cases where it is not. I think using 'int' everywhere is probably a mistake, and using 'int' nowhere is probably also a mistake.

Sometimes you are working within the guaranteed bounds of an 'int' in the C standard, so portability isn't an issue. Sometimes you are mixing different types and want to capture intermediate values in the promoted type, which is dictated by the C standard. Sometimes you want to use the native machine word for a good reason. And sometimes it is just easier to read and reason about (printf-like conversion specifications, code that interfaces with POSIX APIs frequently, etc).

The relevant MISRA rule (which is advisory, not mandatory) includes avoiding size_t, which is absolutely the correct type to use in a lot of pointer situations.
Do you have a source for that? From a quick Google it doesn't seem to prohibit size_t.

Rule 3-9-2 (which is only advisory) says Typedefs that indicate size and signedness should be used in place of the basic numerical types, but this doesn't prohibit use of size_t.

https://forum.misra.org.uk/thread-1130.html

I didn't say it prohibited size_t, only that it suggests avoiding it like other variable-sized types. In practice, compliance checkers will turn advisory rules into warnings, which means every usage of a size_t can potentially become a warning message. It's a lot of unnecessary noise for writing correct code.
>you probably have them in your head as coding guidelines anyway

Ha, if only. Sure, if everyone working on the project is a skilled C dev then yeah, but not when you're piecing together code form a bunch of bodyshops as it often happens in automotive where everything is contracted out to the lowest bidder to push costs down, so MISRA is there as a minimum common denominator to make sure that your code doesn't reach rock bottom in the process.

Ok, fair enough, I'm relatively skilled and I've taught C (and some C++) to people, so I guess I'm biased.

On the other hand - extensive compiler warnings + non-MISRA general-purpose static analysis would probably cover those.

FWIW - usually when interfacing with foreign code like that you match what the foreign code's types than convert it afterwards. Then add an assert [sizeof(t1) >= sizeof(int)] or better a static_assert.

The intent isn't to make your code bullet-proof. It's to move towards a subset of the language with well-defined semantics.

Any safety critical coding standard work I've done always had a waiver process for both MISRA and mccabe-like complexity requirements - the intent is there's always some point where there is diminish returns for compliance.

>always had a waiver process for ... mccabe-like complexity requirements

Also known as "the one trick that your test team doesn't want you to know".

Reminds me of the period in my career I was doing contract work for automotive. I used to work in airbag, highly safety critical real-time stuff, so I knew my way around these kinds of systems.

Mandate came down from customer everything was to be MISRA compliant. Which is great for about 1/2 of your code, just common sense stuff. Like int DebounceVal[NUM_DEBOUNCE_SIGNALS];

For the other half it's essentially impossible, especially when you're writing your own custom tasker/OS (really just a main loop with interrupts) with some simple system calls. The way things were done back then on small 8 and 16-bit systems. You will have serious deviations. There's just no way to disable interrupts without some assembly somewhere.

The "non-programmer" types at our customer kept kicking the code back for not being MISRA compliant. So I had to go through a lot of iterations "fixing" things. Led to code that looked like this

   if (k > ZERO) ...

   n = n + ONE;

   tens_digit = num % TEN;
   hundreds_digit = num % ONE_HUNDRED;
You get the picture.

So code reviews really added no value, because instead of reviewing code (remember these weren't programmers) they just complained about what some automated tool was saying.

After several back and forths I got so sick of things I created a file called MISRA.h to collect all the nonsense defines like this.

   #define ZERO 0
   #define ONE  1
   #define TWO  2
   ...
   #define ONE_HUNDRED 100
As a joke I put this in the title block

   // MISRAble.h
   // (c) copyright blah blah blah
Which of course their eagle eyes caught that, and so the comment itself got flagged by the customer as an open issue. Guess I deserved that one. Funny thing was that they had to track the issue, meeting by meeting, until it was closed out. I think the action item was something like "Remove inappropriate comment from MISRA.h" lol.

As someone else pointed out here... LINT was the bomb. Made code so much better. Especially when you could do stuff like this

   switch (something)
      case A
             ...
             // yes I know I'm falling through to case B
             //lint -fallthrough
      case B:
            ...
Once you got in the habit of appeasing LINT, your code was not only more readable but more explicit.

Good times.

> Once you got in the habit of appeasing LINT, your code was not only more readable but more explicit.

I get the hate, however:

  A) C/C++ sucks at interop.
I don't mean C++01 vs C++13 vs C++15 vs C++17 vs C++19 (and good luck if your project doesn't explicitly specify which type of compiler it needs), I mean Visual Studio vs GCC vs LLVM vs 30 different build flags, compiler versions, target architectures, ABI and caller conventions, linker foibles and just generally one of the most buggy toolchains in existence. A tool like MISRA C doesn't save you from all that exactly, but having a standard at all is important _especially_ when doing security/safety stuff.

In VM (Java/NET/Python/JS) world you have to worry about some library calls and maybe bytecode compatibility, the amount of stuff you don't have to think about is just staggering.

In C/CPP you need to be both disciplined and explicit to safely do just about anything.

  B) Formally provable systems are the dream.
Linter rules do wonders for catching common issues, but they don't catch real bugs very often. The downside of systems like MISRA C etc. is they install a false sense of security "oh it passed the tool so it must be good". In fact you are just _automating ignoring watching for issues_, and worse _obfuscating the code so finding issues is harder_.

But this is why we all hope(d) Rust/Go would save us and produce more reliable software, more cheaply.

C is a very powerful but very primitive language. Too easy to do all sorts of things you didn't intend.

But C was all we had (we didn't even have C++), and we had to use it, and use it effectively to deliver production firmware that couldn't change after it was shipped. We used it, and we got good at it, and we loved it. And we hated it because we used it and loved it, and that gave us the right to hate it.

Like my above example, when looking at tight (production, performant) C code, you always ask "did the programmer mean to do that?" or "was the programmer aware of the side effect" etc.

Once I groked LINT, I understood that the comments which appeased LINT were also messages to us humans that the programmer had considered the side effects, etc. and that this was intended behavior.

Made it easier to look at other people's code, heck even my old code, because it told me that "yeah, this was considered". Saved a lot of guess work, and allowed me to look at the meat of the code and not be distracted by the simple error stuff.

Also, as much as I love C, my biggest complaint is that it should have been strongly typed from the very start. C# gets this right. You have to be very explicit about mangling things in C#. In straight C it's just too easy to mix types and mess simple stuff up. The compilers attitude is, "sure.. I'll compile that". I prefer it asking questions and complaining.

In what world? I have been coding since 1986 and there were systems were C wasn't even an option, and others were C was an option among many others, excluding UNIX's early days before it got widespread outside AT&T.

Even on embedded, besides Assembly, Forth, BASIC and Pascal have been a companion to C since high level languages are able to target those tiny CPUs.

Naturally, I guess there were those systems were the specific vendor only provided a C SDK, which by the end of the MS-DOS days was already rare.

You're taking me too literally. Of course we had assembly, etc.

The kind of development we were working on was safety-critical real-time systems. So assembly is too low level, too risky, you want something more structured. But nothing academic, or insanely expensive. BASIC just isn't up for the task? How do you even write an ISR in BASIC anyways? We had to read two sensors and process a bunch of math every 500 usec, which was like 500 cycles. Every cycle count.

C was for sure the best choice that fit the bill. Plus, everyone knew it. Many times you didn't even get a fully ansi compliant compiler (try writing C on a PIC micro without a software stack).

So, for a while, C ruled in places that mattered. Low cost, performant, and people knew the language.

Also, the compilers we used were purchased from small software vendors, and delivered by mail on floppy discs (this is before the internet took off). Like $3000 for the compiler, a big investment for a small company in 1995. This was back before semiconductor companies had low cost dev tools with free IDEs.

The tools we had were state of the art, expensive, and powerful for their time. We grew to love them, and learned how to work with them better.

The problem wasn't MISRA, but your management.

I've worked with the ISO standards for automotive. You can have exceptions to most rules as long as you have a justification that an independent auditor can agree with.

Oh, and the ISO standards don't mandate MISRA. It's just one option.

Also, the rules only apply where safety is relevant. If you can convincingly show that violating some rule won't affect safety, you were good to go.

There are some pretty hefty rules, though. No recursion. No dynamic allocation unless it can't be avoided (and performance/optimization is not a valid excuse).

> The problem wasn't MISRA, but your management.

Agreed.

But it was my customers management. And the customer is always right yada yada.

So instead of using MISRA to help produce a better deliverable, it gets used as cudgel, a blunt instrument with which to beat programmers with, to create metrics that show the managers are effective at managing.

In these cases, code quality necessarily suffers as a result. Because your resource constrained team has to spend more and more time on busywork.

In my mind, LINT is a better tool than MISRA, because all tools can and will be abused by management, but some tools are more easily abused than others.

Why did your customer have access to your source code? Or was it the case that they didn't but they could access MISRA reports?
We were under contract to deliver them a working design, both hardware and software, which they would then manufacture. Basically outsourced R&D.

So software was part of the deliverable. It was their product, their risk, after all.

We didn't do this for long, just to pay the bills until we had our own products that were making us money.

Makes sense. The approach would then be to inform them that "We'll comply with MISRA, but you'll get poor code quality." It's their choice.

Put yourself in their shoes. As they're not doing the coding, it's riskier for them to sit with you and understand the code and all the exceptions you made to MISRA.

> The approach would then be to inform them that "We'll comply with MISRA, but you'll get poor code quality.

Which we did.

> Put yourself in their shoes.

Oh, I get it. I understand where they were coming from.

My complaint isn't with the customer, its with MISRA being a poor tool and ripe for this kind of abuse.

Because my customer needed to point to something, they point to MISRA, not understanding this isn't entirely helping them.

I loath tools that can be abused this way. Dumbs everyone in the room down. Next you get experts in MISRA that waste everyone's time talking about how wonderful it is.

I'm all for scrutinizing code, but don't waste my time scrutinizing code needlessly.

> My complaint isn't with the customer, its with MISRA being a poor tool and ripe for this kind of abuse.

Any guideline/linter is ripe for abuse. I don't see MISRA being an outlier in this regard. Had everything been developed in house, MISRA wouldn't have been the bottleneck. You'd ignore whatever MISRA rules are not relevant, and note the exception down formally, and be done with it. My job in that company was primarily to understand the automotive ISO guidelines, and inform the team about what was OK and what wasn't, and to write those rationales for the auditor(s). As an extreme example, for one component, the ISO standard allowed us to ignore almost all the guidelines because if it has been tested heavily, it suffices as evidence the component is safe. For that component, we felt it cheaper to test the heck out of it than to conform to the guidelines. We tested it by running it continuously for months, feeding in random inputs. Cheaper to have a computer do it than highly paid engineers getting bogged down.

Even in a contracting scenario, this could have been avoided. You'd write your exceptions, and the customer would get an auditor to go over your exceptions, and be done with it.

Don't blame guidelines for poor organizational behaviors.

As I said elsewhere, the ISO guidelines allow you to be sane about it.

Think of the plus side, though. If you had gone the route you wanted, you would have to read several hundred pages of an ISO standard multiple times before you could be well versed enough to know when it's OK to violate the guidelines. Reading those documents is a lot worse than following MISRA ;-)

> Any guideline/linter is ripe for abuse.

> Don't blame guidelines for poor organizational behaviors.

Oh for sure.

> I don't see MISRA being an outlier in this regard. Had everything been developed in house, MISRA wouldn't have been the bottleneck.

I respect your perspective.

I was in charge of setting in-house guidelines, and tried to do things sanely. Personally I found that the entire team saw much less value in MISRA, but could clearly see the value in LINT. Mainly because MISRA seemed (to us) to be pretty obvious stuff, while LINT seemed to encourage everyone to develop better coding habits.

YMMV

> No dynamic allocation unless it can't be avoided (and performance/optimization is not a valid excuse).

Eliminating dynamic allocation is a performance optimisation, I’d have thought?

Not if it precludes using a more efficient algorithm/mechanism vs the hoops you'd have to jump through to get something functional without it
Sometimes, if your computer has memory to spare, allocating more memory will make the program faster. For example, I've read a paper that found that, for a compiler written in C, allocating tree nodes using arenas and deallocating arenas all at once sped up the program at the cost of more memory usage (compared to maintaining a free list). Allocation was quicker because it was usually just a pointer bump, not navigating a linked list, and deallocation was quicker because the arena chunk was marked as free, not individual nodes.

I imagine it would be similar with eliminating dynamic allocation vs having it. For example, I assume it would be more performant for an implementation of printf to dynamically allocate space for its result and write a whole line to stdout at once than to, for instance, make a system call to write each byte to stdout while avoiding dynamic allocation. I can't think of any better examples for a bare metal context at the moment, though.

No. Eliminating dynamic allocation is to make the program deterministic. The whole point of standards like MISRA or DO-134(b) (for aviation) are to make software deterministic and predictable, not to make them fast. On embedded systems like this, they usually do things like disabling on-chip caches because those are non-deterministic; this of course kills performance, but it doesn't matter.
Such a great story! Thank you for sharing.
a=a; // silence misra unused

Oh the games, oh the kwalitee.

(comment deleted)
Shouldn't that be (void)a;
clang produces explicit warning for that exact statement, while gcc do not.

But well in embedded space there is the another question of how things like IAR behave, which I'm too lazy to test now.

MISRA C is godawful, really bad. It is just a bunch of rules that "sound good" or are good in theory, but I doubt that they actually improve quality at all.

I once worked on a gigantic code base that religiously followed the "no early return" rule. Instead of just returning an error code, everything saved flags and used nested ifs galore.

The code wound along the screen like a dying snake, horizontal scrolling everywhere even with a widescreen monitor.

Refactoring was utterly impossible, fixing simple bugs took months.

I am still baffled how all my colleagues just went along with it and never were like: Wait a second! This is awful!

Jeez. I proposed to adopt some of the Joint Strike Fighter Air Vehicle rules for C++ [1] at one job, and I went through them marking each as "already in practice", "needs debate" or "I'm vetoing it"

And I vetoed early return with a link to the article on railroad error handling. I don't know how it looks in C, but in C++ and Rust early returns are so much better.

[1] https://www.stroustrup.com/JSF-AV-rules.pdf

I deprecate early returns (at least for happy cases). What article are you referring to?
Every time I write an early return (which is every time) I think about my first college lab that had the rules "no global variables, one return statement per function" that if you violated you were failed for the lab.

Luckily our TAs graded those and they cared about as much about those rules as everyone else. Today one is a principle at MS and the other at Google. Guess they knew how to code.

No RAII means that allowing early returns in C can increase the likelihood of not performing cleanup along error paths.

Consider writing a function called connect() which opens a socket and, if successful, calls setsockopt, bind, and friends.

If e.g. bind fails and you choose to return early, then you also have to remember to close the opened socket.

In my own code I prefer to use early returns for all situations except ones like the aforementioned. For those I use a forward goto to a label named "off_ramp" which does the necessary cleanup and returns an error code.

>I am still baffled how all my colleagues just went along with it and never were like: Wait a second! This is awful!

They probably went along if MISRA compliance is pushed by a clueless manager who wants to impress a customer with "our 100% MISRA compliance" and who doesn't want to hear about your 'WHYs' to exceptions to the rules, then all you can do is blindly comply and suffer through while you look for another job. Ask me how I know.

How do you know? (Some anecdotes please?)
Yeah, and this is exactly the problem with standards like MISRA C and the reason I will never work on projects like this ever again.

When rules become more important than reality, everybody directly involved just stops caring.

The early return rule is easy to check, but what about a "don't write code with a mental load so high that nobody will be able to understand it next year" rule?

If the customer wanted actual quality, they would hire dedicated testers and/or write a really good testing harness.

>Yeah, and this is exactly the problem with standards like MISRA C and the reason I will never work on projects like this ever again.

I think it's unfair to point the problem as being with MISRA. MISRA doesn't force you to be 100% compliant with everything there, you're free to pick and choose what you think makes sense for your project and use case and to have your own exception. It's just a set of guidelines, that's it..

The real problem is people(mangers) who don't understand it and want 100% compliance at all cost just because it sounds good and they feel it covers their ass ("nobody ever got fired for being 100% MISRA compliant" /s), instead of investing dev brainstorming time in pruning the list and having a sane review and approval process for exceptions.

It's a problem with moving from engineering mentality to bodyshop mentality.

100% is easy to measure and report on. As soon as you allow even one exception it is really hard to figure out if the exception is really needed, or it is just a bad programmer being lazy at the expense of quality. Management should not have to figure that question out.
>it is really hard to figure out if the exception is really needed, or it is just a bad programmer being lazy at the expense of quality

That's exactly why you have (in self respecting companies) a sane internal standard for exceptions with workshops and trainings, and experienced senior devs review the exception requests, precisely to make sure it's never a lazy dev or junior.

I've worked with some really bad senior devs, and some great junior devs who couldn't do things right because of those senior devs
In Embedded C?
Yes. Companies not known for software can be really bad as the bosses don't know how bad the senior staff is
Never seen such a case. Was it a body shop, semiconductor company, or someone like Google where they put JS devs in charge of writing C?

I worked in an automotive and IOT and the difference between juniors and seniors there was like between us and John Carmack.

As a former embedded C dev I really can't fathom being better than seniors in this gig who have been in the trenches since you can't self study the kind of stuff you encounter in production devices.

To be clear, most senior developers are much better than juniors. However there is a tiny minority that is not.
I guess the implied reason is something like "don't make functions which can accidentally leak resources". "It would probably help to ban early returns."

NO. It won't help. At all.

>The early return rule is easy to check, but what about a "don't write code with a mental load so high that nobody will be able to understand it next year" rule?

Ah, you mean cyclomatic complexity! we have a tool that can check that. It definitely always means a function is too difficult to understand if the cyclomatic complexity is above some arbitrary value and would be safer it is was split up! No exceptions or nuance there!

The recent 2023 version of MISRA C costs just a few pound as PDF. So I got it from $WORK. I don't have a lot of experience with the previous versions. But the 2023 version reads quite okay.

It is not possible to mandate "though shall write good code" because it is not at all clear what that means. Instead they needed to come up with rules that can be checked with automated tools.

The rules are classified as mandatory, required or advisory. Only the mandatory rules cannot be overridden by explicit documentation. As an example, "The goto statement SHOULD not be used" is an advisory rule. Given the bad bad spaghetti code that beginner programmers produce with goto, this is quite reasonable. Even though some code is definitely better with goto. For example to jump to some cleanup before returning.

Similarly, "A function SHOULD have a single point of exit at the end" is an advisory rule only. The rationale for the rule stated in the standard is the following (slightly rephrased):

1. A single point of exit for a function is required by the IEC 61508 and ISO 26262 standards as part of the requirements for a modular approach. 2. Early returns may lead to the unintentional omission of function termination code. 3. If a function has exit points interspersed with statements that produce persistent side effects, it is not easy to determine which side effects will occur when the function is executed.

I'm also a big advocate for early return. But the points from the rationale are valid. And I don't have a good alternative rule that achieves the same goal and is checkable with automated tools...

That's the thing. The rationale sounds good. But where is the evidence?

The code base followed all the rules, yet it was worse and had more bugs than any non-conformant code base I have ever seen.

And teaching beginners to strictly follow rules instead of mercilessly rewriting and testing code until it is sufficiently clean and correct is the worst you can do, IMHO.

Imagine how much worse the code would have been without all those rules.

If people don’t have taste, no rules will get you to good code. Only a bit less worse / better predictable behavior.

> Even though some code is definitely better with goto. For example to jump to some cleanup before returning.

I’d have thought by-now that they’d be mandating dialects of C with try/finally instead of goto for error-handling.

try/finally is non-deterministic; that's why exceptions aren't allowed in DO-134b code. Here's an HN discussion about it: https://news.ycombinator.com/item?id=22483813
try/finally in the context of thrown exceptions, sure.

But in a language without exceptions (like portable C), we can alternatively define try/finally in terms of a "guaranteed execution" block no matter how control returns from a guarded try-block (be it `return`, `break`, `goto`, `longjmp`, etc).

...basically, a safer form of goto. It's possible to implement a form of this in portable C using only preprocessor macros, but it's messy and can't handle some cases, like longjmp.

I see, very interesting. Personally, I'd prefer to just have a "finally:" label; I've never liked "try", because it seems like extra boilerplate, and forces another level of indentation.
That's the rule that stuck with me. Because I hate it.
(comment deleted)
No early return is critical to understanding and maintaining functions of more than 10,000 lines. (probably the limit is around 100 lines, but in my experience functions are either less than 100 lines, or more than 10,000 lines - you rarely see anything in the middle in the real world). I'm a firm believer in not writing long functions so I like early return, but having had to deal with 60,000 line functions early return makes it even harder to figure out how to get someplace or why you didn't.
I honestly think that MISRA ends up creating worse code that's less reliable because it forces programmers into contortions to get even simple stuff done. It's like programming with one hand tied behind your back - and a lot of it seems to be for no good reason.

I'm thoroughly convinced that MISRA was invented by someone who hates programmers and just wants to see them suffer for no reason.

Can't use size_t because it's a variable sized type? Yeah, that's not a win.

Can't do early returns to keep code clean and clear? Now we have to do a heap of flags and nesting, resulting in less maintainable code. I'm so grateful to have my code improved this way.

Even the experts say that "deviations" are sensible and necessary. So what that really means is you shouldn't ever fully comply with MISRA because it's against common sense. And what does that tell you about companies which require MISRA compliance?

Together with the JPL coding standards [0], and the linux kernel coding style [1], this determines a pleasant, safe and sane subset of C. (Note: this does not mean that these rules should be followed blindly in all cases; but one should be familiar with them and understand their purpose before breaking them.)

[0] https://en.wikipedia.org/wiki/The_Power_of_10:_Rules_for_Dev...

[1]https://github.com/torvalds/linux/blob/79c70c304b0b443429b2a...

Might I suggest better alternatives as more pleasant alternatives to the stuffy MISRA C book:

- "Embedded C Coding Standard" by Michael Barr [1], CTO and co-founder of Barr Group, the guy who inspected the ECU code and testified in Toyota's unintended acceleration lawsuit [2]. Good stuff.

- "Embedded System development Coding Reference guide" written by the Software Reliability Enhancement Center of Japan [3], which covers and expands on MISRA C and more. It's a joy in examples, explanations and eye-pleasing aesthetic. I love it.

[1] https://barrgroup.com/sites/default/files/barr_c_coding_stan...

[2] https://www.safetyresearch.net/Library/BarrSlides_FINAL_SCRU...

[3] https://www.ipa.go.jp/publish/qv6pgp00000011mh-att/000065271...

Linux kernel doesn't use a subset; it uses a superset of nearly every GNU C extension. Linux could only be compiled with GCC for so long due to its excessive use of GNU C extensions which tightly bound the codebase to one tool chain. (We fixed this in clang by implementing most GNU C extensions, at least the good ones).

VLAs are banned, those are c99.

I'm sorry, but you've clearly just skimmed this and haven't worked with, nor understood, the purpose of MISRA C. For starters, unless it's changed substantially since I worked with it, MISRA C forbids use of functions like malloc which rules it out for an enormous number of applications. Plus it's a little bit fuzzy in how some of the rules should be interpreted. Like there was one where it says there shouldn't be commented-out fragments of code (our response was to try to parse the lines of each comment, if they succeeded raise a MISRA-specific compile error). There were a few more weird things with MISRA C, but these were a couple that stuck with me.

It's not a "sane" subset of C you can just pick up and run with. It's a narrow subset of C for a very specific use case.

Every place that I’ve worked chooses a subset of MISRA rules to follow and ignores the rest. A rationale for the ignored rules is written in a document somewhere which can be provided to safety auditors.

A deviation for a rule in the “required” set can be made for specific cases.

The “no commented-out fragments of code” is a weird one. It was not required at my previous employer, but it is at my current. It means that you can’t have comments like this without a deviation:

  // This function is only called from
  // main()
Which is much more readable to me than:

  // This function is only called from
  // main
I see what they are trying to prevent, which is people commenting out code instead of removing it. In my experience, code that is commented out usually means that it was committed accidentally, or the author didn’t really understand what the old code was doing so they didn’t delete it entirely.
Yep, I know. But my gripe was someone suggesting MISRA C was something it wasn’t.
I wasn’t disagreeing with you, just adding my 2c :)
MISRA doesn't really "forbid" anything. If you have a variation of the MISRA rules you're supposed to document and justify it.

> like malloc which rules it out for an enormous number of applications.

Malloc is overrated. If your program can't fail you need to have memory for its peak usage. If you do, you can obtain the memory up front and use it.

The Linux coding style in [1] seems to be in love with K&R. It's adamant about indentation being 8 characters (1 tab, not spaces) yet K&R uses 4 character indentation.
MISRA is either an enumeration of specific things that will caused undefined behaviour and you should avoid doing anyway, or a list of arbitrary restrictions which don't really do very much to improve the safety of the code. I think it's mostly worth learning either if you are forced to work with it or as a negative example (of how to write a useful coding standard).
How common is mutation testing in safely critical applications? I wonder if requiring a high mutation testing coverage would be more effective than adhering to misra C. (Though this seems like a ¿Por Qué No Los Dos? situation)

https://en.m.wikipedia.org/wiki/Mutation_testing https://pitest.org/quickstart/mutators/

Safety critical in aerospace uses MCDC as the gold standard for testing.
MCDC is also required in automotive for the highest safety critical components (steering, powertrain, etc)
Same goes for Trains (including high speed trains) in France.
Really sad to see so many people rail on this.

Here's all the examples I've seen shitting on it and why MISRA is correct despite their objections.

1. ""no early return" rule" In C, you have to be really careful with your flow control and memory allocations. So basically ALL good C programmers and styles dictate that you use GOTO's for one location, a "cleanup" location, in which you check and free memory. So if you do

if(!ptr) return;

You do

if(!ptr) { DEBUGLOG("Error allocation ptr");//Also prints out file, line, function etc. bRet = FALSE; goto cleanup; }

Very common, and you can't ever mess up freeing memory. This is why it's used.

2. No magic numbers.

Another person complains about not being able to do magic numbers. While it can get tedious, you really shouldn't. They use the most insane example of #define ONE 1. BUT, even in this case you can argue that if the requirements were changed for a base 8 or other numbers system, you could easily port the app by just changing these define macro's rather than scouring through the code. It's not that tedious, and it can make the codebase more flexible and solve issues and porting.

3. Not able to use 'int'

You should always be explicit with your types. Even rust mainly makes you use sized variables. Why just do int, be specific, you may want int32. The C standard is pretty flexible with things like "long" just being bigger or the same size than "int". So porting things like this makes it robust. You also have the fact that if you do any networking or messages of any kind, this makes the datastructures easily portable since you know exactly what size.

> Very common, and you can't ever mess up freeing memory.

Except that where misra is applied, there often is no dynamic allocation at all.

Most of the times, not always. They sometimes don't use malloc or free but their own special little memory pools.
From my experience in automotive, the ISO standard is against dynamic allocation if your system is classified high enough in terms of their safety levels.

As an example, we weren't allowed to use the C++ string class, and had to find a "safer" string class for C++.

(Not sure about MISRA - most of my experience is with the ISO standard, and MISRA is not mandated by it).

Yeah, so what they do is they don't use free and malloc, but use a bunch of buffers they free. Tadaa! We don't have dynamic allocation, just buffers, see? I've seen it, I can count in AUTOSAR systems like... 20 different hand spun allocators, 10 ques and 50 special little hierarchical state machines.
Forgive my lack of exposure to safety-critical C, but without dynamic allocation how does a program handle large state with indeterminate lifetimes? Or when you need a temporary buffer that’s too big to live on the stack?
You make the stack bigger. You might think I'm joking, but I'm not.

Or you make it static. You can work around it just fine.

> but without dynamic allocation how does a program handle large state with indeterminate lifetimes?

If you haven't, I strongly encourage taking a workshop/course on requirements engineering (not specific to SW nor safety). One thing that stood out is a requirement that says things like "indeterminate" or "large" are red flags. A requirement should state bounds on what sizes it should handle.

> Or when you need a temporary buffer that’s too big to live on the stack?

Do you have an example of where this may occur in a safety critical system?

I've forgotten the details, but many/most forbidden things are allowed in the code provided you have watchdogs for mitigation - so if things did fail it could safely shut down. I don't recall if dynamic allocation fell into that category.

large state is too complicated and error prone. cut it down.

don't put big temp. buffers onto the stack, put them into your .data segment. (i.e. global).

(comment deleted)
MISRA is sometimes unfairly shat on, sure, but some things do require you to use your brain.

Regarding magic numbers, if my memory is not playing tricks on me, MISRA does not include 0 or 1 as magic numbers. Regardless, 1 is 1 regardless the number system you work in, I have never ever seen someone "yes, let's make 1 not be 1" (not in a sane way at least) and defining stuff like that for "improved flexibility" is an antipattern in my book. It makes code bigger, harder to navigate and understand. That kind of code does unexpected stuff, is hard to work with and has more bugs. The less surprises, the less indirection I get in a code base, the better. I also feel it makes refactoring harder not easier. I speak from experience, I've worked with codebases like that, they suck. (Hello Vector, if anyone working for them is out there!)

#define ONE 1 is bad code.

good code would be #define MAX_FOO 1 #define TOTAL_SUPPORTED_BAR_COUNT 1 #define STATUS_BYTE_OFFSET 1

The important thing there is even though all are one we have both given them a unique name, and indicated why they are one.

> 2. No magic numbers. > > Another person complains about not being able to do magic numbers. While it can get tedious, you really shouldn't. They use the most insane example of #define ONE 1. BUT, even in this case you can argue that if the requirements were changed for a base 8 or other numbers system, you could easily port the app by just changing these define macro's rather than scouring through the code. It's not that tedious, and it can make the codebase more flexible and solve issues and porting.

I agree with you on magic numbers in general, but there have to be practical limits.

I was paid to deliver tight production code that was performant, often interfacing with assembly because that was what we had to do with the cheap micro our project used. I wasn't worried about other number systems, it wasn't worth spending R&D $$$ to make the code compatible with other number systems. I'm not writing code that will last 100 years. I deliver to schedules and budgets. Your arguments strike me as a bit too rigid, too academic. Not practical in the real world.

In my mind

   if (condition)
     num_found = num_found + 1
is clearly superior than this MISRA version

   if (condition)
     num_found = num_found + ONE
Because the first case is the simplest implementation, incredibly explicit as it. We're counting something. That's abundantly clear.

The second case is inferior because, as a reviewer doing their job, I have to go and lookup what ONE actually is. In case someone defined it weird. I start thinking "Maybe it's not 1, maybe it's 1.0". or "maybe its fixed point like (1.0 * 256)."

The moment I'm wondering what ONE is actually defined as, I'm no longer thinking about the code I'm reviewing. I have to interrupt my mental process to go find ONE, understand that, and then come back and pick up with what I was reviewing.

This stuff matters. Clarity matters. When 1 might not be 1, well then I have to think harder. Compounded, all these little complications dilutes the quality of the code overall. And programmer resources and time are limited.

Also, not to nitpick but your example "goto cleanup" is also not MISRA compliant. I'd argue that use of gotos is a greater sin than returning early.

I'm pragmatic. I've used gotos, where it makes sense, which is almost never. But never say never. Gotos are nearly always bad. Early returns are useful sometimes. Assembly should be avoided when possible.

Code is a tradeoff. When writing interrupt service routines, performance matters, and I found this is where I broke the MISRA rules the most. Because the code had to be tight. That was the value my company brought to the customer. We could get out of the interrupt quick enough to guarantee deadlines in the rest of the code. You do this by letting yourself write complex code in some places, the places that matter. And you scrutinize those more than you do the rest of the code.

MISRA (as misapplied by mid level managers) pretends the above statement doesn't matter.

> In C, you have to be really careful with your flow control and memory allocations

I was going to ask if an early return could still be called "early" if there were resources to free, but I would say error checking after locking an object would still qualify (releasing the lock being necessary).

C++ helps with this with RAII.

MISRA is there as a standard for safety-critical code. Some contracts, some customers, some regulatory regimes will require compliance with MISRA, or perhaps another embedded coding standard, like AUTOSAR.

Generally, your customer/regulator will allow you to deviate from the standard; you just need to document and explain the deviations.

A static analysis tool like Helix QAC or Klocwork (bias: my company Perforce sells them) will capture your compilation and scan your code in detail to find deviations from the standard. You can then fix those deviations, or use the tool to document and explain them.

>> or perhaps another embedded coding standard, like AUTOSAR.

I can deal with MISRA, it's just a safer subset of C. Most people demanding AUTOSAR compliance don't even know what AUTOSAR was created for, or the fact that those use cases never actually happen in practice. It's so fucked up I won't even work for a company producing AUTOSAR code any more. It's just a hard NO from me because it's an absolute waste of time and money.

MISRA C is total nonsense. If you need that then you should not be using C. Either you can just write the thing in straight assembler while observing the same set of rules and the result will be more readable (or well, COBOL which is more or less an equivalent to C with MISRA C rules) or you should use some high level language intended for that kind of application where the compiler can check for the issues that this ruleset tries to handle, which means Ada (or another application-specific weird Pascal/Modula dialect like CHILL or whatever).