C Part I · Module 9 · Memory and Object Lifetime
Static Objects and Static Storage Duration
The previous chapter listed four ways to return something from a function without handing back a dead pointer, and mentioned a fifth in passing before discouraging it: make the local object outlive the call. That is one keyword, it does exactly what it promises, and it is among the most misused features in C — partly because a lifetime that never ends brings costs that are easy to miss, and partly because the same keyword means something completely unrelated when written in a different place. This chapter covers what the storage duration actually gives you, including one guarantee that automatic objects do not provide and that a surprising amount of C quietly depends on, and then is direct about when reaching for it is the wrong instinct.
Intermediate17 min readCPart IFundamentalsMemory
Part I · Module 9 · Chapter 9.3 · Static Objects and Static Storage Duration
1. A Lifetime That Never Ends
An object with static storage duration exists for the entire run of the program. Its lifetime begins before
mainstarts and ends when the program ends.
Chapter 9.1's Figure 1 drew it as the band spanning the whole timeline. Two kinds of object you have already used have it:
String literals. Chapter 8.2 established this, and it is why return "constant text"; is safe.
Objects declared outside every function. Chapter 5.6 called these file-scope objects and used them without discussing lifetime.
The new thing is that you can ask for it inside a block:
static int calls;2. Lifetime Extended, Scope Unchanged
#include <stdio.h>
int next_id(void)
{
static int counter = 0; /* one object, for the whole program */
counter = counter + 1;
return counter;
}
int fresh_each_time(void)
{
int counter = 0; /* a new object on every call */
counter = counter + 1;
return counter;
}
int main(void)
{
printf("static: %d %d %d\n", next_id(), next_id(), next_id());
printf("automatic: %d %d %d\n",
fresh_each_time(), fresh_each_time(), fresh_each_time());
return 0;
}Output:
static: 1 2 3
automatic: 1 1 1Two functions differing by one keyword. The automatic version is Chapter 5.7 §4's result; the static version keeps its value because there is only ever one object, created before main ran and still alive between calls.
What has not changed is where the name can be used:
void f(void) { static int hidden = 5; }
int main(void) { printf("%d\n", hidden); }error: use of undeclared identifier 'hidden'hidden has block scope, exactly as it would without the keyword. Chapter 9.1 §5 said scope and lifetime are different questions and could not yet show it; this is the demonstration.
staticon a block-scope declaration changes the object's lifetime and nothing about the name's visibility.
That combination is occasionally exactly what you want: state that persists across calls and that nothing outside the function can touch.
3. The Initialiser Runs Once
int probe(void)
{
static int value = 100;
value = value + 1;
return value;
}101 102 103If the initialiser ran on each call, every call would return 101. It does not. The object is initialised once, before the program begins running, and after that the declaration is simply a name for an object that already exists.
One rule follows directly, and it is a real error rather than a style note:
int give(void) { return 5; }
...
static int bad = give();error: initializer element is not a compile-time constantInitialisation happens before any code runs, so there is no "before" in which to call a function. A static object's initialiser must be a constant expression — the same requirement Chapter 4.3 placed on case labels and for the same underlying reason.
4. Zero-Initialisation: the Guarantee Automatic Objects Lack
This is the half of the chapter worth remembering longest.
#include <stdio.h>
int plain_file_scope; /* no initialiser */
static int static_file_scope; /* no initialiser */
int counts_calls(void)
{
static int calls; /* no initialiser */
calls = calls + 1;
return calls;
}
int main(void)
{
printf("plain file-scope, no initialiser: %d\n", plain_file_scope);
printf("static file-scope, no initialiser: %d\n", static_file_scope);
printf("block-scope static across calls: %d %d %d\n",
counts_calls(), counts_calls(), counts_calls());
return 0;
}Output:
plain file-scope, no initialiser: 0
static file-scope, no initialiser: 0
block-scope static across calls: 1 2 3An object with static storage duration and no initialiser is initialised to zero. Pointers become null pointers, and arrays and every member are zeroed throughout.
Compare that with an automatic object, which Chapter 2.3 established holds an indeterminate value in the same situation. This is the single clearest difference between the two durations beyond lifetime itself, and counts_calls relies on it — the counter starts at 0 without anyone writing = 0.
Do write the = 0 anyway. It costs four characters, it states the intent, and it means a reader does not have to know this rule to understand the function.
5. The Keyword Does Two Unrelated Jobs
Look again at the first two lines of §4's program, because together they prove something important.
int plain_file_scope; /* prints 0 */
static int static_file_scope; /* prints 0 */Both are zero. The one without the keyword behaves identically.
That is because every object declared outside a function already has static storage duration, keyword or not. At file scope, static is therefore not what gives the object its lifetime — the lifetime was already there.
| Where you write it | What static changes |
|---|---|
| Inside a block | the object's storage duration — automatic becomes static |
| At file scope | not the storage duration, which is already static — something else entirely |
The "something else" is linkage, which answers whether other source files can refer to the name. That is Chapter 11.4's subject, it requires the idea of a program built from several files, and it has nothing to do with lifetime.
staticis two unrelated features sharing one keyword. Inside a block it is about lifetime; at file scope it is about visibility across files.
Chapter 11.5 is devoted to disentangling the three properties a declaration carries — scope, lifetime and linkage — and exists largely because of this overload. Until then, keep the two uses apart by asking where the declaration is.
6. When to Use It, and When Not
Chapter 9.2 §5 listed this as a fifth way to avoid returning a dead pointer, and immediately discouraged it. Here is the reasoning.
const char *build_name(int n)
{
static char buffer[32]; /* lives forever, so returning it is "safe" */
snprintf(buffer, sizeof buffer, "ch-%02d", n);
return buffer;
}That compiles, returns a usable pointer, and has three costs that are easy not to notice.
There is only one buffer. A second call overwrites the first call's result. A caller holding two results from two calls finds they are the same text.
The function is no longer checkable on its own. Chapter 5.9's criterion was what can I check without running the rest of the program, and a function whose result depends on previous calls fails it. Calling it twice with the same argument is fine; calling it in a different order is not necessarily.
It is not safe to use from more than one thread at a time. Concurrency is outside this curriculum, but it is worth knowing that hidden mutable state is the usual reason a function cannot be used concurrently.
7. What You Should Be Able to Say Now
- Static storage duration means the object exists for the entire run, beginning before
main. - String literals and file-scope objects already have it.
staticinside a block extends the lifetime and leaves the scope alone — the name is still block-scoped.- One object, not one per call: the counter prints 1 2 3 where the automatic version prints 1 1 1.
- The initialiser runs once, before the program starts —
static int value = 100;gives 101, 102, 103. - Therefore the initialiser must be a constant expression; a function call is an error.
- With no initialiser, a static object is zero-initialised — the guarantee automatic objects lack.
- A file-scope object is zero-initialised with or without the keyword, because it already has static duration.
staticmeans two unrelated things: lifetime inside a block, linkage at file scope — Chapter 11.4, synthesised in 11.5.- Use it for state that is genuinely one per program; it is not a fix for a lifetime bug.
8. Exercises
Exercise 1 — give the storage duration. For each, state the storage duration, the scope, and the value before anything is assigned.
int a; /* at file scope */
static int b; /* at file scope */
void f(void) { int c; }
void g(void) { static int d; }
const char *e = "text"; /* what has static duration here? */Exercise 2 — predict the output. Do not compile yet.
int tick(void) { static int n = 10; n++; return n; }
int tock(void) { int n = 10; n++; return n; }
printf("%d %d %d\n", tick(), tick(), tick());
printf("%d %d %d\n", tock(), tock(), tock());Exercise 3 — explain the two zeros. In §4, plain_file_scope and static_file_scope both print 0. Explain why the keyword made no difference, and say what it did change.
Exercise 4 — classify the errors. For each, say whether it compiles and why.
void f(void) { static int x = 5; }
void g(void) { static int x = 2 + 3; }
int h(void) { return 5; }
void i(void) { static int x = h(); }
void j(void) { static int x; x = h(); }The last two differ by very little — say exactly what.
Exercise 5 — find the shared-state bug. Say what this prints and why, then repair it using one of Chapter 9.2 §5's approaches.
const char *name_of(int n)
{
static char buf[16];
snprintf(buf, sizeof buf, "item-%d", n);
return buf;
}
...
printf("%s and %s\n", name_of(1), name_of(2));Exercise 6 — judge four uses. For each, say whether static is the right tool, and if not what you would use instead.
- a counter of how many times a function has been called
- a buffer to return formatted text from a function
- a flag recording that one-time initialisation has run
- a temporary array used inside one call and never again
Exercise 7 — separate the two meanings. Write one declaration where static changes the lifetime and one where it does not, say what it changes in the second case, and name the chapter that covers it.
Exercise 8 — argue the default. A colleague proposes making every function-local buffer static so that "returning them is always safe". Give two concrete failures this would introduce, and say which of Chapter 9.2's options you would recommend instead.
9. Summary
- Static storage duration means existing for the whole run, starting before
main. - Literals and file-scope objects already have it.
- Inside a block,
staticextends the lifetime and leaves the scope unchanged. - There is one object, not one per call — 1 2 3 versus 1 1 1.
- The initialiser runs once and must be a constant expression.
- No initialiser means zero — the guarantee automatic objects do not have.
- A file-scope object is zeroed with or without the keyword, which proves the keyword is doing a different job there.
- That other job is linkage — Chapter 11.4, disentangled in Chapter 11.5.
- Use it for state that is genuinely one per program; it is a poor fix for a lifetime bug, because it trades undefined behaviour for silent sharing.
Three chapters have now described lifetimes without once saying where any of these objects actually are, which has been deliberate — Chapter 9.1 insisted the rule is about time rather than place, and it is. But every working C programmer talks about the stack and the heap, those words describe something real about how implementations behave, and refusing to use them would leave you unable to read anyone else's explanation. The next chapter introduces both as what they are: accurate models of implementation behaviour, with the line between the model and the language rule drawn explicitly.
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C Programming curriculum.
