Skip to main content

`static` and `extern`: Duration, Scope, Linkage

intermediate15 min readLesson 86 of 148

Three distinct meanings of static, what extern really promises, and the storage-duration model Intermediate needs.

static has three faces

static is one keyword with three meanings depending on where it sits. Conflating them is the root of many bugs:

| Where | Meaning | |---|---| | File scope (static int x;) | Internal linkage โ€” private to this TU | | Inside a function (static int c = 0;) | Static storage duration โ€” one object, lives for the whole program, initialized once | | On a function parameter/return (static in _Bool static_flag(...) โ€” rare) | essentially internal linkage for functions |

Inside a function, a static variable is not on the stack. Every call sees โ€” and can mutate โ€” the same object:

int next_id(void) {
    static int counter = 0;   /* born once, survives all calls */
    return ++counter;
}

That is powerful and dangerous: it makes the function stateful, which breaks re-entrancy. Two "simultaneous" uses (from threads, or even recursion) mutate the same storage. Intermediate rule: use function-scope static only for genuinely global, single-instance state, and document it.

extern is a promise

extern int attempts; says: "an object named attempts exists with external linkage โ€” its definition is in some TU, not necessarily this one." It is the bridge that lets many files share one object:

/* stats.c */
int attempts = 0;            /* THE definition (external by default) */

/* stats.h */
extern int attempts;         /* declaration every includer sees */

Every TU that includes the header declares the same external object; exactly one TU defines it. Forget the definition and the linker tells you: undefined reference to 'attempts'.

Storage duration: where objects live

  • Automatic โ€” locals, including parameters. Born at the {, die at the }. A pointer to an automatic object is invalid the moment the block exits.
  • Static โ€” file-scope objects and function-scope static. One instance, whole-program life, zero-initialized if you do not initialize.
  • Allocated โ€” malloc/calloc/realloc. Lives until you free it. Module 3 makes ownership of this category rigorous.

The classic bug this model explains:

char *badge(const char *name) {
    char buf[64];                       /* automatic */
    snprintf(buf, sizeof buf, "%s!", name);
    return buf;                         /* BUG: dangling on return */
}

The returned pointer points into a dead stack frame. static char buf[64]; "fixes" it by making the buffer outlive the call โ€” but now every caller shares one buffer, and a second call overwrites the first result. The real fix is the caller owning the storage (Module 3).

Check your understanding

  • What is printed if you call next_id() three times? (1, 2, 3 โ€” the static persists.)
  • Is extern int x; a definition? (No โ€” a declaration.)
  • Which duration does a string literal have? (Static โ€” it lives for the whole program; that is why returning a literal is safe while returning a local array is not.)

Now practice

Duration & Linkage GymPractice static/extern semantics until the storage model is reflexive.4 challenges ยท ยท ~24 min