Automatic, Static, and Global Variables in C++
Understanding scope, storage duration, lifetime, and linkage
Automatic Variables
An automatic variable is a variable declared inside a block, such as the body of a function. It is also commonly called a local variable. Unless declared static, thread_local, or extern, a local variable normally has automatic storage duration.
The variable is created when program control reaches its declaration and is destroyed when execution leaves the block containing it.
Example 1: Observing the Lifetime of an Automatic Variable
The following program declares var inside the function foo(). It can therefore be accessed only within that function.
File name: automatic_variable.cpp
#include <iostream>
void foo()
{
int var{10};
std::cout << "var has been created and its value is "
<< var << '\n';
}
int main()
{
std::cout << "var does not exist here\n";
foo();
std::cout << "var has been destroyed and no longer exists\n";
return 0;
}
When foo() is called, program control reaches the declaration of var, and the variable is initialized to 10. It remains alive while execution stays within the function.
When foo() returns, var is destroyed. Its name is not visible from main(), so attempting to access it there would produce a compilation error.
- The scope of
vardetermines where its name can be used in the source code. - The lifetime of
varis the period during which its object exists.
Some Facts About Automatic Variables
- The storage required for an automatic variable is managed automatically. No explicit allocation or deallocation is required.
- An automatic variable is created when execution reaches its declaration and destroyed when execution leaves the block containing it.
- The scope of a local automatic variable is restricted to the block in which it is declared.
- A new instance of the variable is created each time the block is entered. Therefore, an automatic variable does not retain its previous value between separate function calls.
- If an automatic variable is not initialized, its initial value may be indeterminate. Reading such a value can result in undefined behavior. It is therefore good practice to initialize local variables when they are declared.
Static Variables
Static variables are different from automatic variables because they have static storage duration. A static variable can be declared inside a function or at namespace scope, outside all functions.
When a static variable is declared inside a function, its name remains local to that function, but its value is preserved between function calls.
Example 2: A Local Static Variable Retaining Its Value
The following program calls next_value() three times. The local static variable retains the value produced by the previous call.
File name: local_static_variable.cpp
#include <iostream>
int next_value()
{
static int value{10};
++value;
return value;
}
int main()
{
std::cout << "value = " << next_value() << '\n';
std::cout << "value = " << next_value() << '\n';
std::cout << "value = " << next_value() << '\n';
return 0;
}
The program produces the following output:
value = 11
value = 12
value = 13
The variable value is initialized only once. The first call increments it from 10 to 11. The second call continues from 11, and the third call continues from 12.
Although value remains alive for the duration of the program, its name can be used only inside next_value().
main() begins. The important observable behavior is that the variable is initialized only once.
Comparing Static and Automatic Local Variables
Consider the same function without the static keyword. In this case, a new variable is created and initialized during every call.
Example 3: An Automatic Variable Recreated for Every Call
File name: automatic_variable_function_calls.cpp
#include <iostream>
int next_value()
{
int value{10};
++value;
return value;
}
int main()
{
std::cout << "value = " << next_value() << '\n';
std::cout << "value = " << next_value() << '\n';
std::cout << "value = " << next_value() << '\n';
return 0;
}
This version produces the following output:
value = 11
value = 11
value = 11
Here, value is an automatic variable. A new instance is initialized to 10 every time next_value() is called. It is then incremented to 11 and destroyed when the function returns. Therefore, each call returns 11.
A local static variable behaves differently. It is initialized once and retains its value between calls.
| Property | Automatic Local Variable | Static Local Variable |
|---|---|---|
| Storage duration | Automatic | Static |
| Initialization | Each time execution reaches its declaration | Once |
| Value retained | No | Yes |
| Scope | Enclosing block | Enclosing block |
| Lifetime | Until execution leaves the block | Until program termination |
Initialization of Static Variables
A variable with static storage duration is zero-initialized if no other static initialization applies. For example:
static int count;
The initial value of count is 0.
An implementation will commonly place an uninitialized or zero-initialized static variable in a BSS-like section, while a static variable with a nonzero initial value may be placed in an initialized data section. A programmer should not rely on these section names unless working with a specific platform, compiler, linker, or embedded-system memory layout.
Some Facts About Static Variables
- A static variable can be declared inside a function or at namespace scope.
- A block-scope static variable has local scope. Its name cannot be accessed outside the block in which it is declared.
- A namespace-scope variable declared with
statichas internal linkage. Its name refers to an entity that is available only within the current translation unit. - A static variable has static storage duration. Its storage lasts for the duration of the program.
- Objects with static storage duration are normally destroyed during program termination. Therefore, it is more accurate to say that their lifetime extends until program termination, rather than saying that they are never destroyed.
- Since C++11, initialization of a block-scope static variable is thread-safe. If multiple threads attempt to initialize the same local static variable concurrently, exactly one thread performs the initialization while the others wait for it to complete.
C++20: constinit
C++20 introduced constinit for variables with static or thread storage duration. It instructs the compiler to verify that the variable is statically initialized.
constinit int maximum_connections{100};
Unlike const, constinit does not make the variable immutable. Its value can still be changed during execution. Its purpose is to prevent unintended dynamic initialization.
Global Variables
The term global variable commonly refers to a variable declared at namespace scope, outside all functions and classes.
A namespace-scope variable can be accessed by functions that appear after its declaration in the same translation unit. Unless its linkage is restricted, it may also be referred to from other translation units through an appropriate declaration.
Example 4: Sharing a Global Variable Between Functions
The following program demonstrates that changes made to a global variable by one function are visible to another function.
File name: global_variable.cpp
#include <iostream>
int value{10};
void increment_and_display()
{
std::cout << "value inside increment_and_display() = "
<< value << '\n';
++value;
}
int main()
{
++value;
increment_and_display();
std::cout << "value inside main() = " << value << '\n';
return 0;
}
The program produces the following output:
value inside increment_and_display() = 11
value inside main() = 12
The variable value is declared outside all functions. Its initial value is 10.
The main() function increments it to 11. When increment_and_display() is called, the function sees the updated value and prints 11. It then increments the variable again. When program control returns to main(), the value is 12.
Both functions access the same object. Changes made through one reference to that object are therefore visible through the other.
Accessing a Global Variable from Another Source File
To refer to the same global variable from another translation unit, it can be defined in one source file and declared with extern in another.
The variable could be defined in one source file:
int value{10};
Another source file could declare it as follows:
extern int value;
The extern declaration does not create another variable. It declares that the variable is defined elsewhere.
Some Facts About Global Variables
- A global variable is declared at namespace scope, outside all functions and classes.
- A non-
constnamespace-scope variable normally has external linkage unless its linkage is explicitly restricted. - A namespace-scope variable declared with
statichas internal linkage and is restricted to its translation unit. - Global variables have static storage duration and normally remain alive until program termination.
- A global variable can be shared between functions and, when it has suitable linkage, between translation units.
- Global variables should be used carefully because any code with access to them may modify their state. This can make dependencies less visible and make a program harder to test and maintain.
Summary
Automatic, static, and global variables differ mainly in their scope, storage duration, lifetime, and linkage. The following table brings these differences together.
| Variable | Typical Declaration | Scope | Lifetime |
|---|---|---|---|
| Automatic local | int value{10}; inside a function |
Enclosing block | Until execution leaves the block |
| Static local | static int value{10}; inside a function |
Enclosing block | Until program termination |
| Global | int value{10}; at namespace scope |
Namespace scope | Until program termination |
Discover more from Tech For Talk
Subscribe to get the latest posts sent to your email.
Leave a Reply