Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Understanding Local Variable Scope and Lifetime
Understand when local variables are created, destroyed, and accessible within blocks.
Local variables
In the Introduction to local scope lesson, we introduced local variables, which are variables defined inside a function (including function parameters).
C++ doesn't actually have a single defining characteristic that makes a variable "local". Instead, local variables possess several distinct properties that differentiate their behavior from other types of (non-local) variables. We'll explore these properties in this and subsequent lessons.
In the Introduction to local scope lesson, we also introduced the concept of scope. An identifier's scope determines where within the source code that identifier can be accessed. When an identifier is accessible, we say it is in scope. When it cannot be accessed, we say it is out of scope. Scope is a compile-time property, and attempting to use an identifier when it is out of scope results in a compile error.
Local variables have block scope
Local variables have block scope, which means they are in scope from their point of definition until the end of the block in which they are defined.
Related content: Please review the Compound statements (blocks) lesson if you need a refresher on blocks.
int main()
{
int temperature{72}; // temperature enters scope here
double pressure{14.7}; // pressure enters scope here
return 0;
} // pressure and temperature go out of scope here
Although function parameters aren't defined inside the function body, for typical functions they can be considered part of the scope of the function body block.
int calculateMinimum(int first, int second) // first and second enter scope here
{
// determine the smaller of first or second
int minimum{(first < second) ? first : second}; // minimum enters scope here
return minimum;
} // minimum, second, and first leave scope here
Variable names within a scope must be unique
Variable names must be unique within a given scope, otherwise any reference to that name becomes ambiguous. Consider this program:
void someFunction(int temperature)
{
int temperature{}; // compilation failure due to name collision with function parameter
}
int main()
{
return 0;
}
This program doesn't compile because the variable temperature defined inside the function body and the function parameter temperature have identical names and both exist in the same block scope.
Local variables have automatic storage duration
A variable's storage duration (commonly just called duration) determines what rules govern when and how a variable will be created (instantiated) and destroyed. In most cases, a variable's storage duration directly determines its lifetime.
Related content: We discuss what a lifetime is in the Introduction to local scope lesson.
For example, local variables have automatic storage duration, which means they are created at the point of definition and destroyed at the end of the block in which they are defined. For example:
int main()
{
int temperature{72}; // temperature created and initialized here
double pressure{14.7}; // pressure created and initialized here
return 0;
} // pressure and temperature are destroyed here
Because of this property, local variables are sometimes called automatic variables.
Local variables in nested blocks
Local variables can be defined inside nested blocks. This behaves identically to local variables in function body blocks:
int main() // outer block
{
int score{100}; // score enters scope and is created here
{ // nested block
int bonus{25}; // bonus enters scope and is created here
} // bonus goes out of scope and is destroyed here
// bonus cannot be used here because it is out of scope in this block
return 0;
} // score goes out of scope and is destroyed here
In the example above, variable bonus is defined inside a nested block. Its scope is limited from its point of definition to the end of that nested block, and its lifetime matches this scope. Since the scope of variable bonus is restricted to the inner block where it's defined, it cannot be accessed anywhere in the outer block.
Note that nested blocks are considered part of the scope of the outer block in which they are defined. Consequently, variables defined in the outer block can be accessed inside a nested block:
#include <iostream>
int main()
{ // outer block
int score{100}; // score enters scope and is created here
{ // nested block
int bonus{25}; // bonus enters scope and is created here
// score and bonus are both in scope here
std::cout << score << " + " << bonus << " = " << score + bonus << '\n';
} // bonus goes out of scope and is destroyed here
// bonus cannot be used here because it is out of scope in this block
return 0;
} // score goes out of scope and is destroyed here
Local variables have no linkage
Identifiers have another property called linkage. An identifier's linkage determines whether a declaration of that same identifier in a different scope refers to the same object (or function).
Local variables have no linkage. Each declaration of an identifier with no linkage refers to a unique object or function.
For example:
int main()
{
int count{10}; // local variable, no linkage
{
int count{20}; // this declaration of count refers to a different object than the previous count
}
return 0;
}
Scope and linkage may appear similar at first glance. However, scope determines where a declaration of a single identifier can be seen and used in the code. Linkage determines whether multiple declarations of the same identifier refer to the same object or not.
Related content: We discuss what happens when variables with the same name appear in nested blocks in the Variable shadowing (name hiding) lesson. Linkage isn't particularly interesting in the context of local variables, but we'll explore it more in the next few lessons.
Variables should be defined in the most limited scope
If a variable is only used within a nested block, it should be defined inside that nested block:
#include <iostream>
int main()
{
// do not define bonus here
{
// bonus is only used inside this block, so define it here
int bonus{50};
std::cout << bonus << '\n';
}
// otherwise bonus could still be used here, where it's not needed
return 0;
}
By limiting a variable's scope, you reduce program complexity because the number of active variables is minimized. Furthermore, it makes it easier to see where variables are used (or aren't used). A variable defined inside a block can only be used within that block (or nested blocks). This can make programs easier to understand.
If a variable is needed in an outer block, it must be declared in the outer block:
#include <iostream>
int main()
{
int bonus{50}; // we're declaring bonus here because we need it in this outer block later
{
int input{};
std::cin >> input;
// if we declared bonus here, immediately before its actual first use...
if (input == 10)
bonus = 100;
} // ... it would be destroyed here
std::cout << bonus; // and we need bonus to exist here
return 0;
}
The example above demonstrates one of the rare cases where you may need to declare a variable well before its first use.
New developers sometimes wonder whether it's worth creating a nested block just to intentionally limit a variable's scope (and force it to go out of scope / be destroyed early). Doing so makes that individual variable simpler, but the overall function becomes longer and more complex as a result. The tradeoff generally isn't worth it. If creating a nested block seems useful to intentionally limit the scope of a chunk of code, that code might be better placed in a separate function instead.
Define variables in the most limited existing scope. Avoid creating new blocks whose only purpose is to limit the scope of variables.
Summary
- Local variables: Variables defined inside a function (including function parameters)
- Block scope: Local variables are in scope from their point of definition until the end of the block in which they're defined
- Scope vs accessibility: When an identifier is accessible, it's "in scope"; when it cannot be accessed, it's "out of scope"
- Compile-time property: Scope is determined at compile time; using an out-of-scope identifier causes a compile error
- Unique names in scope: Variable names must be unique within a given scope to avoid ambiguity
- Automatic storage duration: Local variables are created at their point of definition and destroyed at the end of the block
- Automatic variables: Local variables are sometimes called "automatic variables" due to their automatic storage duration
- Nested blocks: Variables defined in nested blocks have scope limited to that nested block
- Outer block access: Variables from outer blocks can be accessed in nested blocks
- No linkage: Local variables have no linkage; each declaration refers to a unique object
- Scope vs linkage: Scope determines where a single identifier can be seen; linkage determines whether multiple declarations refer to the same object
- Limited scope principle: Define variables in the most limited existing scope to reduce complexity and improve code understanding
- Avoid artificial nesting: Don't create new blocks solely to limit variable scope; consider extracting code into separate functions instead
Local variables are fundamental to C++ functions, providing automatic memory management through block scope and automatic storage duration. Defining variables in the smallest possible scope reduces program complexity by minimizing the number of active variables at any point, making code easier to understand and maintain.
Understanding Local Variable Scope and Lifetime - Quiz
Test your understanding of the lesson.
Practice Exercises
Local Variable Lifetime
Understand when local variables are created and destroyed by tracking their construction and destruction.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!