Scope, Lifetime and static

Every variable has a scope — where in the code its name can be used — and a lifetime — how long its memory exists. Understanding both explains local and global variables, the many meanings of static, and a whole family of pointer bugs.

Block scope

A variable declared inside braces exists only from its declaration to the closing brace:

C
#include <stdio.h>

int main(void) {
    int total = 0;
    for (int i = 1; i <= 3; i++) {
        int squared = i * i;     // new each pass
        total += squared;
    }
    // i and squared don't exist here
    printf("total = %d\n", total);
    return 0;
}
Output
total = 14

Declare variables in the smallest scope that works. Fewer variables in view means fewer chances to use the wrong one.

Shadowing

An inner declaration with the same name hides the outer one. It's legal but confusing; -Wshadow warns about it:

C
#include <stdio.h>

int main(void) {
    int count = 10;
    {
        int count = 99;
        printf("inner: %d\n", count);
    }
    printf("outer: %d\n", count);
    return 0;
}
Compiler warnings
main.c:6:13: warning: declaration shadows a local variable [-Wshadow]

Automatic lifetime

Local variables have automatic storage: they're created when the block is entered and destroyed when it ends (they usually live on the stack). That's why a function must never return the address of one of its local variables — the memory is gone once the function returns:

C
#include <stdio.h>

int *make_number(void) {
    int n = 42;
    return &n;
}

int main(void) {
    int *p = make_number();
    printf("%d\n", *p);
    return 0;
}
Compiler warnings
main.c:5:13: warning: address of stack memory associated with local variable 'n' returned [-Wreturn-stack-address]

Using such a "dangling" pointer is undefined behaviour — it may even print 42 by luck, then fail later. Return the value itself, use a caller-provided buffer, or allocate memory with malloc (lesson 36).

File scope: global variables

A variable declared outside any function has file scope and static lifetime: it exists for the whole run of the program, and starts at zero if not initialised.

C
#include <stdio.h>

int requests_served;   // global, starts at 0

void serve(void) {
    requests_served++;
}

int main(void) {
    serve();
    serve();
    serve();
    printf("served %d requests\n", requests_served);
    return 0;
}
Output
served 3 requests

Globals are convenient but make programs harder to reason about: any function can change them, so bugs are hard to trace. Prefer passing values as parameters. Reasonable uses include program-wide constants and configuration set once at startup.

static local variables

static on a local variable gives it static lifetime: it's initialised once and keeps its value between calls, while staying private to the function:

C
#include <stdio.h>

int next_ticket(void) {
    static int counter = 100;   // initialised once, before the program starts
    return counter++;
}

int main(void) {
    for (int i = 0; i < 3; i++) {
        printf("Ticket %d\n", next_ticket());
    }
    return 0;
}
Output
Ticket 100
Ticket 101
Ticket 102

static at file scope

On a global variable or function, static means something different: internal linkage — the name is private to this source file and can't be seen from other files. It's C's way of making things "private" in multi-file programs (lesson 52).

C
#include <stdio.h>

static int cache_hits = 0;             // only visible in this file

static void record_hit(void) {         // helper, private to this file
    cache_hits++;
}

int main(void) {
    record_hit();
    record_hit();
    printf("%d hits\n", cache_hits);
    return 0;
}
Output
2 hits

Summary

DeclaredScopeLifetimeStarts as
In a blockThe blockUntil the block endsIndeterminate (garbage)
static in a blockThe blockWhole program0, or the initialiser (once)
Outside functionsThe rest of the file (and other files via extern)Whole program0, or the initialiser
static outside functionsThe rest of this file onlyWhole program0, or the initialiser
Allocated with mallocReached through pointersUntil freeIndeterminate (calloc: zero)

Try it yourself

Write a function that returns a running average: each call adds one value and returns the average of all values so far, using static variables.

Try it yourself · main.c
Edit the code, copy it into main.c, then compile and run it with cc -std=c17 -Wall main.c -o main && ./main. How to set up a C compiler
Output
after 10: 10.00
after 20: 15.00
after 60: 30.00
after 30: 30.00