`static` and `extern`: Duration, Scope, Linkage
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 youfreeit. 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.)