Skip to content
VLSI Mentor

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 main starts 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    static int calls;

2. Lifetime Extended, Scope Unchanged

Azvya Education Pvt. Ltd.VLSI Mentor
the same counter, two storage durations
#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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
static:    1 2 3
automatic: 1 1 1

Two 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    void f(void) { static int hidden = 5; }
    int main(void) { printf("%d\n", hidden); }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

static on 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

Azvya Education Pvt. Ltd.VLSI Mentor
not once per call
int probe(void)
{
    static int value = 100;
    value = value + 1;
    return value;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
101 102 103

If 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int give(void) { return 5; }
    ...
    static int bad = give();
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: initializer element is not a compile-time constant

Initialisation 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.

Azvya Education Pvt. Ltd.VLSI Mentor
what you get when you supply nothing
#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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
plain file-scope, no initialiser:  0
static file-scope, no initialiser: 0
block-scope static across calls:   1 2 3

An 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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 itWhat static changes
Inside a blockthe object's storage duration — automatic becomes static
At file scopenot 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.

static is 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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

  1. Static storage duration means the object exists for the entire run, beginning before main.
  2. String literals and file-scope objects already have it.
  3. static inside a block extends the lifetime and leaves the scope alone — the name is still block-scoped.
  4. One object, not one per call: the counter prints 1 2 3 where the automatic version prints 1 1 1.
  5. The initialiser runs once, before the program starts — static int value = 100; gives 101, 102, 103.
  6. Therefore the initialiser must be a constant expression; a function call is an error.
  7. With no initialiser, a static object is zero-initialised — the guarantee automatic objects lack.
  8. A file-scope object is zero-initialised with or without the keyword, because it already has static duration.
  9. static means two unrelated things: lifetime inside a block, linkage at file scope — Chapter 11.4, synthesised in 11.5.
  10. 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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, static extends 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.