Automatic, Static, and Global Variables in C++

Understanding scope, storage duration, lifetime, and linkage

Vivek Bhadra  |  C++ Variables Storage Duration

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.

Technical note: Automatic variables are often stored on the program’s call stack, but the C++ language does not require a stack-based implementation. The compiler may store a variable elsewhere or eliminate its storage entirely during optimization.

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.

Scope and lifetime are not the same:
  • The scope of var determines where its name can be used in the source code.
  • The lifetime of var is the period during which its object exists.

Some Facts About Automatic Variables

  1. The storage required for an automatic variable is managed automatically. No explicit allocation or deallocation is required.
  2. An automatic variable is created when execution reaches its declaration and destroyed when execution leaves the block containing it.
  3. The scope of a local automatic variable is restricted to the block in which it is declared.
  4. 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.
  5. 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().

Initialization detail: For a block-scope static variable that requires dynamic initialization, initialization occurs the first time program control reaches its declaration. If the initializer is a constant initializer, as it is in this example, the implementation can perform static initialization before 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.

About BSS and data sections: The C++ language specifies zero-initialization, but it does not require a variable to be stored in a section named BSS. BSS and data sections are implementation details commonly used by compilers, linkers, and executable-file formats.

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

  1. A static variable can be declared inside a function or at namespace scope.
  2. A block-scope static variable has local scope. Its name cannot be accessed outside the block in which it is declared.
  3. A namespace-scope variable declared with static has internal linkage. Its name refers to an entity that is available only within the current translation unit.
  4. A static variable has static storage duration. Its storage lasts for the duration of the program.
  5. 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.
  6. 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.

C++17 enhancement: There must normally be exactly one definition of a non-inline global variable in the program. C++17 introduced inline variables, which allow the same inline variable definition to appear in multiple translation units, usually through a header file.

Some Facts About Global Variables

  1. A global variable is declared at namespace scope, outside all functions and classes.
  2. A non-const namespace-scope variable normally has external linkage unless its linkage is explicitly restricted.
  3. A namespace-scope variable declared with static has internal linkage and is restricted to its translation unit.
  4. Global variables have static storage duration and normally remain alive until program termination.
  5. A global variable can be shared between functions and, when it has suitable linkage, between translation units.
  6. 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