6. Statements
6.1 Selection Statements
6.1.1 Enclose the body of a selection or an iteration statement in a compound statement
Follow each control flow primitive (if, else, while, for, do and switch) by a block enclosed by braces, even if the block is empty or contains only one line. Use of null statements or statement expressions in these contexts reduces code readability and making it harder to maintain.
#include <cstdint> void doSomething (); void foo (int32_t i) { if (0 == i) doSomething (); // @@- Non-Compliant -@@ else ; // @@- Non-Compliant -@@ if (0 == i) { // @@+ Compliant +@@ doSomething (); } else { // @@+ Compliant +@@ } switch (i) case 0: doSomething (); // @@- Non-Compliant -@@ }
References
HIC++ v3.3 – 5.1
6.1.2 Explicitly cover all paths through multi-way selection statements
Make sure that each if-else-if chain has a final else clause, and every switch statement has a default clause. The advantage is that all execution paths are explicitly considered, which in turn helps reduce the risk that an unexpected value will result in incorrect execution.
#include <cstdint> void foo (int32_t i) { // @@- Non-Compliant: missing else clause to cover <tt>0 == i</tt> path -@@ if (i > 0) { } else if (i < 0) { } // @@- Non-Compliant: missing default clause -@@ switch (i) { case 0: break; case 1: break; } }
References
- HIC++ v3.3 - 5.11
6.1.3 Ensure that a non-empty case statement block does not fall through to the next label
Fall through from a non-empty case block of a switch statement makes it more difficult to reason about the code, and therefore harder to maintain.
#include <cstdint> void foo (int32_t i) { switch (i) { case 0: // @@+ Compliant +@@ case 1: ++i; break; case 2: // @@- Non-Compliant -@@ ++i; default: break; } }
References
- HIC++ v3.3 – 5.4
6.1.4 Ensure that a switch statement has at least two case labels, distinct from the default label
A switch statement with fewer than two case labels can be more naturally expressed as a single if statement.
#include <cstdint> void doSomething (); void doSomethingElse (); void foo (int32_t i) { // @@- Non-Compliant: 1 case label -@@ switch (i) { case 0: doSomething (); break; default: doSomethingElse (); break; } // @@+ Compliant: an equivalent if statement +@@ if (0 == i) { doSomething (); } else { doSomethingElse (); } // @@- Non-Compliant: only 1 case label distinct from the default label -@@ switch (i) { case 0: doSomething (); break; case 1: default: doSomethingElse (); break; } // @@+ Compliant: 2 case labels distinct from the default label +@@ switch (i) { case 0: case 1: doSomething (); break; default: doSomethingElse (); break; } }
Note: By virtue of this rule and Rule <hicpp ref=”intro.execution.reachable”/>, switch statements with a boolean condition should not be used.
void bar (bool b) { switch (b) { case true: break; case false: break; default: break; // @@- Non-Compliant: unreachable statement -@@ } switch (b) { case true: break; case false: default: break; // @@- Non-Compliant: only 1 case label distinct from the default label -@@ } }
References
- JSF AV C++ Rev C – 195
- JSF AV C++ Rev C – 196
- MISRA C++:2008 – 6-4-7
- MISRA C++:2008 – 6-4-8
6.2 Iteration Statements
6.2.1 Implement a loop that only uses element values as a range-based loop
A range-based for statement reduces the amount of boilerplate code required to maintain correct loop semantics. A range-based loop can normally replace an explicit loop where the index or iterator is only used for accessing the container value.
#include <iterator> #include <cstdint> void bar () { uint32_t array[] = { 0, 1, 2, 3, 4, 5, 6 }; uint32_t sum = 0; // @@- Non-Compliant -@@ for (uint32_t * p = std::begin (array); p != std::end (array); ++p) { sum += *p; } sum = 0; // @@+ Compliant +@@ for (uint32_t v : array ) { sum += v; } // @@+ Compliant +@@ for (size_t i = 0; i != (sizeof(array)/sizeof(*array)); ++i) { if ((i % 2) == 0) // Using the loop index { sum += array[i]; } } }
6.2.2 Ensure that a loop has a single loop counter, an optional control variable, and is not degenerate
A loop is considered ‘degenerate’ if:
- When entered, the loop is infinite, or
- The loop will always terminate after the first iteration.
To improve maintainability it is recommended to avoid degenerate loops and to limit them to a single counter variable.
#include <cstdint> int32_t foo (); void bar (int32_t max) { // @@- Non-Compliant: 2 loop counters -@@ for (int32_t i (0), j (foo ()) ; (i < max) && (j > 0); ++i, j = foo ()) { } bool keepGoing (true); // @@+ Compliant: 1 loop counter and 1 control variable +@@ for (int32_t i (0) ; keepGoing && (i < max); ++i) { keepGoing = foo () > 0; } for (int32_t i (0) ; i < max; ++i) // @@+ Compliant +@@ { if (foo () <= 0) { break; } } }
References
- MISRA C++:2008 - 6-5-1
- MISRA C++:2008 - 6-5-6
6.2.3 Do not alter a control or counter variable more than once in a loop
The behavior of iteration statements with multiple modifications of control or counter variables is difficult to understand and maintain.
#include <cstdint> void foo() { for ( int32_t i (0); i != 10; ++i ) //@@- Non-Compliant: does this loop terminate? -@@ { if ( 0 == i % 3 ) { ++i; } } }
References
- HIC++ v3.3 – 5.6
6.2.4 Only modify a for loop counter in the for expression
It is expected that a for loop counter is modified for every iteration. To improve code readability and maintainability, the counter variable should be modified in the loop expression.
#include <cstdint> bool foo (); void bar (int32_t max) { for (int i (0) ; i < max; ) // @@- Non-Compliant -@@ { if (foo ()) { ++i; } } }
References
- HIC++ v3.3 - 5.5
6.3 Jump Statements
6.3.1 Ensure that the label(s) for a jump statement or a switch condition appear later, in the same or an enclosing block
Backward jumps and jumps into nested blocks make it more difficult to reason about the flow through the function. Loops should be the only constructs that perform backward jumps, and the only acceptable use of a goto statement is to jump forward to an enclosing block.
#include <cstdint> void f1 (int32_t i) { start: // @@- Non-Compliant -@@ ++i; if (i < 10) { goto start; } } void f2 (int32_t i) { do // @@+ Compliant: Same as 'f1' using do/while loop +@@ { ++i; } while (i < 10); } bool f3 (int (&array)[10][10]) { for (int j1 = 0; j1 < 10; ++j1) { for (int j2 = 0; j2 < 10; ++j2) { if (array[j1][j2] == 0) { goto finished; } // ... } } finished: // @@+ Compliant +@@ return true; }
Control can also be transferred forward into a nested block by virtue of a switch label. Unless case and default labels are placed only into the top level compound statement of the switch, the code will be difficult to understand and maintain.
#include <cstdint> void f1 (int32_t i) { switch (i) { case 0: break; default: if (i < 0) { case 1: // @@- Non-Compliant: jump into a nested block -@@ break; } break; } }
References
- HIC++ v3.3 - 5.8
6.3.2 Ensure that execution of a function with a non-void return type ends in a return statement with a value
Undefined behavior will occur if execution of a function with a non-void return type (other than main) flows off the end of the function without encountering a return statement with a value.
#include <cstdint> int32_t foo (bool b) { if (b) { return -1; } } // @@- Non-Compliant: flows off the end of the function -@@
Exception
The main function is exempt from this rule, as an implicit return 0; will be executed, when an explicit return statement is missing.
References
- HIC++ v3.3 – 5.10
6.4 Declaration Statement
6.4.1 Postpone variable definitions as long as possible
To preserve locality of reference, variables with automatic storage duration should be defined just before they are needed, preferably with an initializer, and in the smallest block containing all the uses of the variable.
#include <cstdint> int32_t f1 (int32_t v) { int32_t i; // @@- Non-Compliant -@@ if ((v > 0) && (v < 10)) { i = v * v; --i; return i; } return 0; } int32_t f2 (int32_t v) { if ((v > 0) && (v < 10)) { int32_t i (v*v); // @@+ Compliant +@@ --i; return i; } return 0; }
The scope of a variable declared in a for loop initialization statement extends only to the complete for statement. Therefore, potential use of a control variable outside of the loop is naturally avoided.
#include <cstdint> int32_t f3 (int32_t max) { int32_t i; for (i = 0; i < max; ++i) // @@- Non-Compliant -@@ { } return i; } void f4 (int32_t max) { for (int32_t i (0); i < max; ++i) // @@+ Compliant +@@ { } }
References
- HIC++ v3.3 - 5.12
- HIC++ v3.3 - 8.4.4