Why "continue" is considered as a C violation in MISRA C:2004?
It is because of the ancient debate about goto
, unconditional branching and spaghetti code, that has been going on for 40 years or so. goto
, continue
, break
and multiple return
statements are all considered more or less equally bad.
The consensus of the world's programming community has roughly ended up something like: we recognize that you can use these features of the language without writing spaghetti code if you know what you are doing. But we still discourage them because there is a large chance that someone who doesn't know what they are doing are going to use the features if they are available, and then create spaghetti. And we also discourage them because they are superfluous features: you can obviously write programs without using them.
Since MISRA-C is aimed towards critical systems, MISRA-C:2004 has the approach to ban as many of these unconditional branch features as possible. Therefore, goto
, continue
and multiple returns were banned. break
was only allowed if there was a single break inside the same loop.
However, in the "MISRA-C:2011" draft which is currently under evaluation, the committee has considered to allow all these features yet again, with a restriction that goto should only be allowed to jump downwards and never upwards. The rationale from the committee said that there are now tools (ie static analysers) smart enough to spot bad program flow, so the keywords can be allowed.
The goto debate is still going strong...
Programming in C makes it notoriously hard to keep track of multiple execution branches. If you allocate resources somewhere, you have to release them elsewhere, non-locally. If your code branches, you will in general need to have separate deallocation logic for each branch or way to exit a scope.
The continue
statement adds another way to exit from the scope of a for
loop, and thus makes such a loop harder to reason about and understand all the possible ways in which control can flow through it, which in turn makes it harder to ascertain that your code behaves correctly in all circumstances.
This is just speculation on my part, but I imagine that trying to limit complexity coming from this extra branching behaviour is the driving reason for the rule that you mention.
I've just run into it. We have items, which
- should be checked for several things,
- checks require some preparation,
- we should apply cheap checks first, then go with expensive ones,
- some checks depends others,
- whichever item fails on any check, it should be logged,
- if the item passes all the checks, it should be passed to further processing.
Watch this, without continue:
foreach (items) {
prepare check1
if (check1) {
prepare check2
if (check2) {
prepare check3
if (check3) {
log("all checks passed")
process_good_item(item)
} else {
log("check3 failed")
}
} else {
log("check2 failed")
}
} else {
log("check 1 failed")
}
}
...and compare with this, with continue:
foreach (items) {
prepare check1
if (!check1) {
log("check 1 failed")
continue
}
prepare check2
if (!check2) {
log("check 2 failed")
continue
}
prepare check3
if (!check3) {
log("check 3 failed")
continue
}
log("all checks passed")
process_good_item(item)
}
Assume that "prepare"-s are multiple line long each, so you can't see the whole code at once.
Decide yourself, which is
- less complex, have a simpler execution graph
- have lower cyclomatic complexity value
- more readable, more linear, no "eye jumps"
- better expandable (e.g. try to add check4, check5, check12)
IMHO Misra is wrong in this topic.