Local variables in C++ are attached to the scope and destroyed when the end of the scope is reached. Any access to a variable outside of their
scope has undefined behavior.
Such access occurs, for example, when the address of a variable is stored in a pointer that is later dereferenced:
int func() {
int* ptr = nullptr;
{
int i = 10;
ptr = &i;
} // variable i goes out of scope here
*ptr = 10; // Noncompliant: writing to out-of-scope-variable
}
A similar defect can occur in code that does not have curly braces (also referred to as a compound statement), but contain control structures, like
if
or for
that also introduce scope:
int exampleWithIf() {
int* ptr;
if (int i = 10)
ptr = &i;
else
ptr = nullptr;
// variable i declared in if condition statement goes out of scope here
if (ptr)
return *ptr; // Noncompliant: reading from out-of-scope variable
return 0;
}
void exampleWithFor() {
int* ptr = nullptr;
for (int i = 0; i < 10; ++i)
ptr = &i;
// variable i defined in for init-statement goes out of scope here
*ptr = 10; // Noncompliant
}
What is the potential impact?
Accessing a dangling reference or pointer causes undefined behavior. This means the compiler is not bound by the language standard anymore and your
program has no meaning assigned to it.
Practically this has a wide range of effects. In many cases, the access works by accident and succeeds at writing or reading a value. However, it
can start misbehaving at any time. If compilation flags, compiler, platform, or runtime environment change, the same code can crash the application,
corrupt memory, or leak a secret.
Why is the issue raised for reference variables?
When a reference variable is directly initialized to a temporary object, such temporary is lifetime-extended by the variable, i.e., the temporary
object is destroyed when the variable goes out of scope. Lifetime-extended temporaries have the same behavior as if they were declared as local
variables and may lead to the same issues. For example:
Clazz create();
void refExtension(Clazz const arg) {
Clazz const* aPtr;
Clazz const* tPtr;
{
Clazz const& aRef = arg; // bounding reference to object arg
Clazz const& tRef = create(); // temporary object is created here and bound to reference,
// behaves as Clazz const tRef = create();
aPtr = &aRef; // points to arg
tPtr = &tRef; // point to a temporary object that is lifetime extended
} // both aRef and tRef go out of scope here, because tRef was extending the lifetime of
// temporary variable, the object is destroyed
aPtr->foo(); // OK, a points to arg
tPtr->foo(); // Noncompliant: the pointers point to a dangling temporary
}