Skip to content
Laviyx Learn

Try

C++ course menu

C++ Scope and Lifetime: Local, Global and static Variables

Understand scope and lifetime in C++: block scope, local variables, shadowing, global variables and why to avoid them, static local variables that keep their value between calls, and dangling references to local variables.

Beginner8 min readUpdated
On this page

Two questions apply to every variable:

  • Scope: where in the code can I use this name?
  • Lifetime: when does the variable exist in memory?

Understanding both explains many compiler errors, and some of the nastiest run-time bugs in C++.

Block scope#

A variable declared inside a block { } can be used only from its declaration to the end of that block:

C++
#include <iostream>

int main() {
    int total = 0;
    for (int i = 1; i <= 3; ++i) {
        int square = i * i;     // exists only inside the loop body
        total += square;
    }
    std::cout << total << '\n';
    // std::cout << square;     // error: 'square' was not declared in this scope
    // std::cout << i;          // error: i belongs to the for loop
}
Output
14

Declaring variables in the smallest scope that needs them keeps code tidy and prevents accidental reuse.

Local variables in functions#

Each function has its own local variables. Two functions can use the same name without conflict, and parameters are local variables too:

C++
#include <iostream>

int double_it(int value) {
    int result = value * 2;
    return result;
}

int main() {
    int result = double_it(21);   // a different 'result'
    std::cout << result << '\n';
}
Output
42

A local variable is created each time the function is called and destroyed when it returns. Its value is not remembered between calls.

Shadowing#

An inner block can declare a name that already exists outside it. The inner name hides (shadows) the outer one:

C++
#include <iostream>

int main() {
    int level = 1;
    {
        int level = 99;      // shadows the outer level
        std::cout << "inner: " << level << '\n';
    }
    std::cout << "outer: " << level << '\n';
}
Output
inner: 99
outer: 1

Shadowing is legal but confusing. Compile with -Wshadow to get a warning, and choose different names.

Global variables#

A variable declared outside every function is global: visible in the whole file, and alive for the whole program:

C++
#include <iostream>

int request_count = 0;   // global

void handle_request() {
    ++request_count;
}

int main() {
    handle_request();
    handle_request();
    std::cout << request_count << '\n';
}
Output
2

Global variables are convenient but risky: any function can change them, so when a value is wrong, the cause could be anywhere. Prefer passing values as parameters and returning results. Global constants (constexpr double pi = 3.14159;) are fine, because nobody can change them.

static local variables#

A local variable marked static is created once, the first time the function runs, and keeps its value between calls. Its scope is still the function, so nothing else can touch it:

C++
#include <iostream>

int next_ticket() {
    static int counter = 100;   // initialized only once
    return ++counter;
}

int main() {
    std::cout << next_ticket() << '\n';
    std::cout << next_ticket() << '\n';
    std::cout << next_ticket() << '\n';
}
Output
101
102
103

Without static, counter would be recreated as 100 on every call, and every ticket would be 101.

Lifetime and dangling references#

When a local variable's lifetime ends, its memory is reused. A reference or pointer that still refers to it is dangling, and using it is undefined behaviour:

C++
int& broken() {
    int value = 42;
    return value;     // warning: reference to local variable returned
}

The caller gets a reference to memory that no longer holds value. It might print 42 by luck, print garbage, or crash. The fix is simple: return by value:

C++
#include <iostream>

int fixed() {
    int value = 42;
    return value;     // the value is copied (or moved) out safely
}

int main() {
    std::cout << fixed() << '\n';
}
Output
42

Returning large objects by value is efficient in modern C++, because the compiler constructs the result directly in the caller (copy elision) or moves it.

Summary table#

KindScopeLifetime
Local variableIts blockUntil the block ends
ParameterIts functionUntil the function returns
static localIts functionWhole program (from first call)
GlobalWhole file (or program)Whole program

Common mistakes#

Practice#

Practice exercise

Write a function log_message(const std::string& text) that prints each message with a running number, like [1] Started, [2] Loading, without using a global variable. Call it three times.

Show solution
C++
#include <iostream>
#include <string>

void log_message(const std::string& text) {
    static int number = 0;
    ++number;
    std::cout << "[" << number << "] " << text << '\n';
}

int main() {
    log_message("Started");
    log_message("Loading");
    log_message("Done");
}
Output
[1] Started
[2] Loading
[3] Done

Key takeaway#

Finished this lesson?