CScope & Lifetime

Scope & Lifetime

Two closely related but distinct ideas govern every variable in C: scope (where in the source code a name is visible) and lifetime, also called storage duration (how long the variable's memory actually exists while the program runs). It's entirely possible for a name to go out of scope while its memory is still alive, or for a name to be in scope pointing at memory that no longer exists — both are common sources of bugs, so it's worth understanding the two concepts separately.

Kinds of scope
  • Block scope — a variable declared inside { } (a function body, an if, a for loop, or any nested block) is only visible from its declaration to the closing brace of that block.

  • File scope — a variable or function declared outside every function, at the top level of a source file, is visible from its declaration to the end of that file (and possibly other files too, via extern).

  • Function scope — this term applies specifically to labels used with goto; a label is visible throughout its entire enclosing function, regardless of which block it's declared in.

C
int file_scope_var = 100; /* visible to every function below, in this file */

void demo(void) {
    int block_scope_var = 1; /* visible only inside demo() */

    if (block_scope_var == 1) {
        int nested_var = 2; /* visible only inside this if-block */
        goto done;           /* labels have function scope, not block scope */
    }

done:
    return;
}
Kinds of lifetime (storage duration)

Storage duration

When memory is created

When memory is destroyed

Automatic

When execution enters the enclosing block

When execution leaves the enclosing block

Static

Once, before main() starts running

When the program terminates

Allocated (dynamic)

When you call malloc/calloc/realloc

When you call free (or the program ends)

Plain local variables have automatic storage duration — this is the same auto storage class covered on the previous page. Globals and static locals have static storage duration. Memory you request with malloc has its own lifetime entirely under your control, covered later in the memory management section.

Scope vs lifetime — a worked example

Here's the key experiment that makes the distinction concrete: an ordinary local variable declared inside a loop is recreated every iteration (new scope entry means new automatic lifetime), while a static variable in the same spot is created exactly once and simply keeps its value:

C
#include <stdio.h>

int main(void) {
    for (int i = 0; i < 3; i++) {
        int fresh = 0;    /* automatic duration -- reborn every iteration */
        static int kept = 0; /* static duration -- created once, ever */

        fresh++;
        kept++;

        printf("iteration %d: fresh = %d, kept = %d\n", i, fresh, kept);
    }
    return 0;
}
iteration 0: fresh = 1, kept = 1
iteration 1: fresh = 1, kept = 2
iteration 2: fresh = 1, kept = 3

Both fresh and kept have exactly the same scope — they're only nameable inside the loop body — but wildly different lifetimes. fresh is allocated and destroyed three separate times; kept is allocated once before the loop even starts running and lives until the program exits, quietly remembering its value between iterations even though its name is inaccessible outside the loop body.

A pointer can outlive the scope it was set in
A dangerous variant of this mismatch: returning the address of an automatic local variable from a function. The variable's *scope* ends when the function returns, and so does its *lifetime* — the memory is no longer valid — but a pointer that still holds that address doesn't know that, and dereferencing it is undefined behavior. This is exactly why functions should never return a pointer to a plain local variable (returning a pointer to a `static` or heap-allocated variable is fine, since those outlive the function call).