C Mastery / Scope, Storage Duration, and Lifetime
Part 1 — The Core Language

Scope, Storage Duration, and Lifetime

This chapter separates three concepts that beginners frequently conflate: scope (where a name is visible), storage duration (how long storage is guaranteed to exist), and lifetime (the period during which an object can be accessed). Nearly all pointer and memory bugs trace back to confusing these.

Why This Matters

"Use-after-free," "dangling pointer," and "returning a pointer to a local" are all lifetime bugs, not scope bugs. Understanding the difference between "the name is out of scope" and "the object is dead" is what lets you reason correctly about memory.

Prerequisites

Core Concept

Scope

Scope is the region of program text where an identifier is visible (can be used). It is a *compile-time, lexical* property.

C has four kinds of scope:

ScopeWhere the name is visible
File scopeFrom its declaration to the end of the translation unit (top-level names)
Block scopeFrom its declaration to the end of the enclosing block ({ })
Function prototype scopeWithin a function prototype's parameter list
Function scopeA label, visible throughout the function (only for labels)

Storage duration

Storage duration determines how long the storage for an object is reserved. It is a property of the object, not the name.

Storage durationMeaning
StaticStorage exists for the entire program run
AutomaticStorage exists from block entry until block exit
ThreadStorage exists for the lifetime of the thread (C11, _Thread_local)
AllocatedStorage exists from malloc/calloc/realloc until free (or program end)

Lifetime

Lifetime is the period during which an object is guaranteed to have its storage and be accessible.

until exit from the block (with nuances for VLAs and goto).

allocation call until free.

The critical rule: accessing an object outside its lifetime is undefined behavior.

Syntax

Automatic storage (the default inside blocks)

void f(void)
{
    int x = 1;   /* automatic storage: lives until end of f */
}

Static storage

int global_var;          /* static storage, external linkage */
static int file_var;     /* static storage, internal linkage */

void f(void)
{
    static int call_count = 0;  /* static storage, block scope */
    call_count++;
}

Thread-local storage (C11)

#include <threads.h>

_Thread_local int per_thread_state;  /* or thread_local from <threads.h> */

Allocated storage

#include <stdlib.h>

void f(void)
{
    int *p = malloc(sizeof *p);  /* allocated storage */
    if (p != NULL) {
        *p = 5;
        free(p);                 /* end of lifetime */
    }
}

Examples

Scope vs. lifetime, demonstrated

#include <stdio.h>

int *make_dangling(void)
{
    int local = 42;   /* automatic: dies at end of make_dangling */
    return &local;    /* DANGER: returns pointer to dead object */
}

int main(void)
{
    int *p = make_dangling();
    /* printf("%d\n", *p);  /* UNDEFINED BEHAVIOR: use after lifetime */
    (void)p;
    return 0;
}

This is a classic bug. local is still "in scope" lexically inside the function, but once make_dangling returns, its lifetime ends.

Static local keeps its value

#include <stdio.h>

int counter(void)
{
    static int n = 0;
    return ++n;
}

int main(void)
{
    printf("%d %d %d\n", counter(), counter(), counter());
    return 0;
}

Expected output is 1 2 3 (assuming left-to-right evaluation; the order of evaluation of arguments is unspecified in C, but with three calls to the same function the *calls* happen in some order — this example is best understood by calling in separate statements to avoid evaluation-order subtleties). See c.ops.sequencing.

Better:

int a = counter();
int b = counter();
int c = counter();
printf("%d %d %d\n", a, b, c);  /* 1 2 3 */

How It Works

objects go in the data/bss sections (or rodata for const), automatic objects go on the stack, allocated objects come from the heap.

A name going out of scope does not necessarily end an object's lifetime (e.g., a static local remains alive after its block exits). Conversely, an object's lifetime can end while a name still refers to it (e.g., free(p) while p is still in scope).

Variations

VLAs and goto

A variable-length array (c.arr.vla) has automatic storage but its lifetime begins when its declaration is reached, not at block entry. Jumping into the scope of a VLA with goto is a constraint violation in some cases.

Compound literals

A compound literal at block scope has automatic lifetime; at file scope it has static lifetime (c.struct.compound-literal).

Common Mistakes

the function returns.

"immutable."

Undefined Behavior

non-unsigned char type) can be UB or an unspecified value (c.obj.indeterminate).

Portability

thread_local is a macro from <threads.h>.

detail, but the *guarantee* of zero initialization for static storage is standard.

Under the Hood

initialized before main runs (zero for bss, explicit values for data).

and deallocate them at block entry/exit.

This mapping is developed fully in c.memory.1.

Practical Usage

locals.

to a function.

created them, and establish clear ownership (c.mem.ownership).

Exercises

1. Write a function with a static local and a function with an automatic local; call each several times and explain the difference. 2. Write a function that returns a pointer to a local, then demonstrate the bug by running it (it may "work" or crash — explain why either is possible). 3. For each of these, state scope, storage duration, and lifetime: a file-scope int, a block-scope static int, a block-scope automatic int, and a malloced int.

Deep Challenge

Explain, using the C standard's definitions of scope, storage duration, and lifetime, exactly why this program's behavior is undefined and at what point the undefined behavior occurs:

#include <stdlib.h>

int *g;

void store(void)
{
    int x = 7;
    g = &x;
}

int main(void)
{
    store();
    return *g;   /* ? */
}

Then rewrite it so the same value is accessible after store returns, in two correct ways (one using static storage, one using allocated storage).

References

(memory management).

Verification

standard. VERIFIED

VERIFIED

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.lang.scope05
c.lang.storage-duration06
c.lang.lifetime06