Variables should be initialized before their use to avoid unexpected behavior due to garbage values.
Why is this an issue?
A local variable of any built-in type (such as int
, float
, and pointers), declared without an initial value is not
initialized to any particular value. Consequently, if no value is assigned to such a variable first, the code that uses it has no defined
behavior.
int addition() {
int x; // x is not initialized
return x + 10; // Noncompliant: x has grabage value
}
int dereference() {
int* p; // p is not initialized
return *p; // Noncompliant: p has garbage value
}
Similarly, structures that simply aggregate variables of built-in types, such as arrays or struct
/class
types without a
constructor, will not initialize their members when declared without an initializer:
struct Aggregate {
int i;
float f;
};
void aggregates() {
int* intArray[5]; // each element of array is not initializer
Aggregate aggr; // members aggr.i, agrr.f are not initialized
Aggregate aggrArray[2]; // members of each element are not initialized
}
Finally, allocating objects of builtin or such aggregates types on the heap, also does not initialize their values:
void usingMalloc() {
int* intArr = (int*)malloc(sizeof(int) * 10); // each of 10 allocated integers is not initialized
}
This also applies when new
is used in C++:
void usingNew() {
Aggregate* aggrPtr = new Aggregate; // members of allocated Aggregate are not initialized
Aggregate* aggrArr = new Aggregate[5]; // members of each of 5 Aggregate objects are not initialized
}
What is the potential impact?
Using garbage values will cause the program to behave nondeterministically at runtime. The program may produce a different output or crash
depending on the run.
In some situations, loading a variable may expose a sensitive data, such as a password that was previously stored in the same location, leading to
a vulnerability that uses such a defect as a gadget for extracting information from the instance of the program.
Finally, in C++, outside of a few exceptions related to the uses of unsigned char
or std::byte
, loading data from an
uninitialized variable causes undefined behavior. This means that the compiler is not bound by the language standard anymore, and the program has no
meaning assigned to it. As a consequence, the impact of such a defect is not limited to the use of garbage values.
Why is there an issue for a class with a default constructor?
In C++, a class can define a default constructor invoked when an object of the given type is created. Such a constructor is called even if a
variable is declared without any initializer. However, if the constructor code omits the initialization of a member that itself does not have the
default constructor, the member will remain uninitialized (See also S2107). And reading from it will produce a garbage value:
struct Partial {
// x is not initialized
Partial() : y(10.0) {}
int x;
float y;
};
int initialized() {
Partial p; // constructor is called
// or even Partial p{};
return p.x; // Non-compliant: reading an uninitialized variable
}
Exceptions
This rule does not flag the variables with static storage duration, meaning: global, static, and thread-local variables.
All the variables with static storage duration are zero-initialized before the initializer is evaluated. As a consequence, any variable or member
of such an object has a defined value even if no initializer is specified.
int globInt;
int globTab[10];
Aggregate globAggr;
Partial globPart; // x member is zero-initialized
int uses() {
static int staticInt;
return globInt // Compliant: all zero-initialized
+ globTab[2]
+ globAggr.f
+ globPart.x
+ staticInt;
}
How to fix it
Commonly, the use of an uninitialized object is an indication of a defect in the code, where either variable initialization was skipped on some
code paths, or the object is used by mistake. Generally, you can address such problems by:
- Initializing variables on the declaration with a valid value
- Assigning to the variable on the code path(s) that was missing initialization
Whenever possible, it is preferable to initialize a variable with its final value on the declaration, as this eliminates the possibility of this
defect occurring.
Code examples
Noncompliant code example
int function(int flag, int b) {
int a;
if (flag) {
a = b;
}
return a; // Noncompliant: "a" has not been initialized in all paths
}
Compliant solution
Initializing variable on all code paths:
int function(int flag, int b) {
int a;
if (flag) {
a = b;
} else {
a = 10;
}
return a; // Compliant
}
Skipping path that leads to the read of an uninitialized value:
int function(int flag, int b) {
int a;
if (flag) {
a = b;
} else {
return 10;
}
return a; // Compliant
}
Providing a valid initial value:
int function(int flag, int b) {
int a = 10;
if (flag) {
a = b;
}
return a; // Compliant
}
Initializing value in the definition:
int function(int flag, int b) {
int const a = flag ? b : 10;
return a; // Compliant
}
Pitfalls
Initializing the variable to zero at the declaration is not always the right solution to fix the issue, as it may lead to logic errors if such a
value is not handled correctly. For example, setting an age
field of an Employee
structure may break assumptions of
retirement handling code. Or more commonly, setting a pointer to NULL
will turn dereference of an uninitialized value into a null-pointer
dereference.
Going the extra mile
With the addition of lambdas in C++11, it is possible to initialize variables on declaration without creating a separate function to compute the
value:
int x = [&] { // capture all context by reference
if (someCondition())
return computeVar1();
/* perform more computations */
return other;
}(); // invoke the lambda immediately (right after creation)
Such pattern is referred to as an Immediately invoked function expression (IIFE) or Imediately invoked lambda. Furthermore, with
the addition of structured binding in C++17, it is possible to declare multiple variables whose values are coupled:
auto [px, py, pz] = [&] {
if (x_dir) {
return std::make_tuple(1, 0, 0);
} else if (y_dir) {
return std::make_tuple(0, 1, 0);
} else {
assert(z_dir);
return std::make_tuple(0, 0, 1);
}
}();
Resources
Documentation
External coding guidelines
- CWE - 457 Use of Uninitialized Variable
- MISRA C:2004, 9.1 - All automatic variables shall have been assigned a value before being used.
- MISRA C++:2008, 8-5-1 - All variables shall have a defined value before they are used.
Related rules
- S2107 detects fields being left uninitialized after the invocation of a constructor