4. Standard Conversions
4.1 Array-to-Pointer Conversion
4.1.1 Ensure that a function argument does not undergo an array-to-pointer conversion
When an array is bound to a function parameter of pointer type the array is implicitly converted to a pointer to the first element of the array. In order to retain the array dimension, the parameter should be changed to a reference type or changed to a user defined type such as std::array.
#include <cstdint> #include <array> void f (int32_t a[10]); // parameter is of pointer type void g (int32_t a[]); // parameter is of pointer type void h (int32_t * a); void i (int32_t (&a) [10]); void j (std::array<int32_t, 10> & a); void foo () { int32_t a1 [10]; std::array<int32_t, 10> a2; f(a1); // @@- Non-Compliant -@@ g(a1); // @@- Non-Compliant -@@ h(a1); // @@- Non-Compliant -@@ i(a1); // @@+ Compliant +@@ j(a2); // @@+ Compliant +@@ }
References
- JSF AV C++ Rev C – 97
- MISRA C++:2008 – 5-2-12
4.2 Integral Conversions
4.2.1 Ensure that the "U" suffix is applied to a literal used in a context requiring an unsigned integral expression
If a literal is used to initialize a variable of unsigned type, or with an operand of unsigned type in a binary operation, the U suffix should be appended to the literal to circumvent an implicit conversion, and make the intent explicit.
#include <cstdint> void foo () { uint32_t u (0); // @@- Non-Compliant -@@ u += 2; // @@- Non-Compliant -@@ u += 2U; // @@+ Compliant +@@ }
References
- HIC++ v3.3 – 6.1
- MISRA C++:2008 – 2-13-3
4.2.2 Ensure that data loss does not demonstrably occur in an integral expression
Data loss can occur in a number of contexts:
- Implicit conversions
- Type casts
- Shift operations
- Overflow in signed arithmetic operations
- Wraparound in unsigned arithmetic operations
If possible, integral type conversions should be avoided altogether, by performing all operations in a uniform type matched to the execution environment. Where data storage is a concern, type conversions should be localized with appropriate guards (e.g. assertions) to detect data loss. Similar techniques can be used to guard shift and arithmetic operations, especially where the data is tainted in a security sense, i.e. a malicious user can trigger data loss with appropriately crafted input data.
#include <climits> #include <stdexcept> #include <cstdint> uint32_t inv_mult (uint32_t a, uint32_t b) { return ((0 == a) || (0 == b)) ? UINT_MAX : (1000 / (a * b)); // @@- Non-Compliant: could wraparound -@@ } void foo () { inv_mult (0x10000u, 0x10000u); } uint32_t safe_inv_mult (uint32_t a, uint32_t b) { if ((b != 0) && (a > (UINT_MAX / b))) { throw std::range_error ("overflow"); } return ((0 == a) || (0 == b)) ? UINT_MAX : (1000 / (a * b)); // @@+ Compliant: wraparound is not possible +@@ }
Data loss may also occur if high order bits are lost in a left-shift operation, or the right-hand operand of a shift operator is so large that the resulting value always 0 or undefined regardless of the value of the left hand operand. Therefore, appropriate safeguards should be coded explicitly (or instrumented by a tool) to ensure that data loss does not occur in shift operations.
#include <cstdint> void foo (uint8_t u) { uint32_t v = u >> 8U; // @@- Non-Compliant: always 0 -@@ v <<= 32U; // @@- Non-Compliant: undefined behavior -@@ v = 0xF1234567U << 1; // @@- Non-Compliant: high bit is lost -@@ }
For the purposes of this rule integral to bool conversions are considered to results in data loss as well. It is preferable to use equality or relational operators to replace such type conversions. The C++ language standard states that unless the condition of an if, for, while or do statement has boolean type, it will be implicitly converted to bool.
#include <cstdint> int32_t foo (); void bar (int32_t i) { if (i) // @@- Non-Compliant -@@ { } if (i != 0) // @@+ Compliant +@@ { } for (int32_t j (10); j ; --j) // @@- Non-Compliant -@@ { } while (int32_t j = foo ()) // @@- Non-Compliant -@@ { } }
Note: An implicit conversion using an operator bool declared as explicit does not violate this rule.
References
- HIC++ v3.3 - 5.2
- HIC++ v3.3 - 8.4.13
- HIC++ v3.3 - 10.7
- HIC++ v3.3 - 10.12
- HIC++ v3.3 - 10.13
4.3 Floating Point Conversions
4.3.1 Do not convert an expression of wider floating point type to a narrower floating point type
The C++ standard defines three floating point types: float, double, long double that, at least conceptually, increase in precision. Expressions that implicitly or explicitly cause a conversion from long double type to float or double, and from double to float should be avoided as they may result in data loss. When using a literal in a context that requires type float, use the F suffix, and for consistency use the L suffix in a long double context.
void foo () { float f (1.0); // @@- Non-Compliant -@@ f = 1.0F; // @@+ Compliant +@@ double d (1.0L); // @@- Non-Compliant -@@ d = 1.0; // @@+ Compliant +@@ long double ld (1.0); // @@+ Compliant, but not good practice +@@ ld = 1.0L; // @@+ Compliant +@@ f = ld; // @@- Non-Compliant -@@ d = ld; // @@- Non-Compliant -@@ f = d; // @@- Non-Compliant -@@ d = f; // @@+ Compliant +@@ ld = f; // @@+ Compliant +@@ ld = d; // @@+ Compliant +@@ }
References
- HIC++ v3.3 – 6.2
- HIC++ v3.3 – 10.14
4.4 Floating-Integral Conversions
4.4.1 Do not convert floating values to integral types except through use of standard library functions
An implicit or explicit conversion from a floating to an integral type can result in data loss due to the significant difference in the respective range of values for each type. Additionally, floating point to integral type conversions are biased as the fractional part is simply truncated instead of being rounded to the nearest integral value. For this reason use of standard library functions: std::floor and std::ceil is recommended if a conversion to an integral type is necessary.
#include <cstdint> #include <cmath> void foo (double d) { int32_t i = d; // @@- Non-Compliant, fraction is truncated -@@ i = d + 0.5; // @@- Non-Compliant, number is rounded -@@ i = std::floor (d); // @@+ Compliant, fraction is truncated +@@ i = std::floor (d + 0.5); // @@+ Compliant, number is rounded +@@ }
Note: A return value of std::floor and std::ceil is of floating type, and an implicit or explicit conversion of this value to an integral type is permitted.
References
- HIC++ v3.3 – 7.6