Skip to content
VLSI Mentor

C Part I · Module 9 · Memory and Object Lifetime

Object Lifetime in C

Two earlier chapters set up a question and deliberately declined to answer it. Chapter 5.7 built the frame picture that explains how calls work, then spent its closing section insisting that C never mentions a stack and that what the language actually specifies is a lifetime rule — named, and handed here. Chapter 8.2 showed a function safely returning a pointer to a string literal and a function catastrophically returning a pointer to a local array, and said the vocabulary that distinguishes them belongs to this module. Both were waiting for one idea, and it is an idea about time rather than about memory. An object exists for a bounded portion of your program's execution; which portion is decided by a property of its declaration; and using it outside that window is undefined regardless of where the bytes happen to be.

Intermediate17 min readCPart IFundamentalsMemory

Part I · Module 9 · Chapter 9.1 · Object Lifetime in C

1. The Question Two Modules Deferred

Chapter 5.7 showed that a local object disappears when its function returns, and demonstrated it: the same function called three times printed the same number three times, because each call got a fresh object. It then said, carefully, that C does not mention a stack — what it specifies is a rule about when objects exist, and that rule was this module's.

Chapter 8.2 §7 produced the pair that makes the question urgent:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    const char *always_safe(void)   { return "constant text"; }
    const char *never_do_this(void) { char local[] = "text"; return local; }

Both compile. Both return a const char *. One is fine and one is broken, and nothing about the types distinguishes them.

What distinguishes them is how long the thing at the far end of the pointer continues to exist.

2. Lifetime

The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address, and retains its last-stored value throughout its lifetime.

Two things follow immediately, and both are worth stating before any of the detail.

Lifetime is a span of time, not a place. The definition says nothing about where the bytes are. It is entirely about when.

Using an object outside its lifetime is undefined behaviour. Not "returns an old value" and not "probably still works" — the standard imposes no requirements, exactly as in Chapter 6.4 and Chapter 7.4.

That second point is the whole reason the module exists, and it is worth noticing what it does not say. It does not say the storage is wiped, or that the address becomes invalid in any detectable way. It says the guarantee is withdrawn. Chapter 9.10 is about why that distinction makes these bugs so persistent.

3. Storage Duration

Lifetime is not something you set directly. It is determined by a property of the declaration called storage duration:

Storage durationLifetimeChapter
automaticfrom entry to its block until that block exits9.2
staticthe entire run of the program9.3
allocatedfrom the allocation call until you release it9.6
threadthe lifetime of a threadnot covered in this curriculum

Three of those you already use constantly without having the name.

Every local variable and every parameter in this curriculum so far has had automatic storage duration. Every string literal has had static storage duration — which is exactly why always_safe works. Allocated is the one that does not exist yet, and arrives in Chapter 9.6.

The fourth row is listed so that you know it exists rather than meeting it later and assuming this table was incomplete. Thread storage duration was added in C11 and belongs to concurrent programming, which this curriculum does not cover.

A timeline of program execution running left to right, from the start of the program to the end. Beneath it, three bands whose widths represent lifetimes. Static storage duration spans the entire timeline. Allocated storage duration spans a shorter interval inside it, from the allocation call until the release call. Automatic storage duration spans a shorter interval still, lasting only while its block is executing.program execution, left to rightstart of the program → end of the programstatic storage durationthe whole run · 9.3allocated storage durationmalloc until free · 9.6automatic storage durationwhile its block runs · 9.212
Figure 1 — the three storage durations as spans of program execution. The width of each band is its lifetime, not its size and not its position in memory. A static object exists for the entire run. An allocated object exists from the call that creates it until the call that releases it, which can be any sub-span. An automatic object exists only while its block is executing, which is usually the shortest span of the three and is always nested inside the run. Nothing in this figure says anything about where any of these objects are stored, because the rule does not.

4. Lifetime Is Bounded by the Block

The commonest misunderstanding is that a local object lives as long as its function. It does not — it lives as long as its block, which Chapter 4.1 introduced and Chapter 5.6 made precise.

Azvya Education Pvt. Ltd.VLSI Mentor
where a lifetime actually begins and ends
#include <stdio.h>

int main(void)
{
    printf("outer begins\n");

    {
        int inner = 1;
        printf("  inner exists, holds %d\n", inner);
    }

    printf("inner's lifetime has ended\n");

    for (int pass = 0; pass < 3; pass++) {
        int per_pass = 0;
        per_pass = per_pass + 1;
        printf("  pass %d: per_pass = %d\n", pass, per_pass);
    }

    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
outer begins
  inner exists, holds 1
inner's lifetime has ended
  pass 0: per_pass = 1
  pass 1: per_pass = 1
  pass 2: per_pass = 1

inner ceased to exist at the closing brace of its block, with main still running — so lifetime is clearly not tied to the function.

The loop is the sharper demonstration. per_pass is 1 on every pass, not 1, 2, 3. Each iteration enters the block, which begins a new lifetime for a new object, initialises it to 0, increments it, and then leaves the block, ending that lifetime. Three passes, three objects, each existing for one iteration.

This is the same result Chapter 5.7 §4 obtained with three separate calls, arrived at without any calls at all — which is a useful cross-check that the rule is about blocks and not about function boundaries.

Entering a block begins the lifetime of the automatic objects declared in it. Leaving the block ends them.

5. Scope and Lifetime Are Different Questions

Chapter 5.6 was careful to separate these and to say that Chapter 11.5 would finish the job. The distinction matters here, so it is worth restating precisely:

AsksDecidedProperty of
ScopeWhere in the text can this name be used?at compile timethe name
LifetimeWhen does the object exist?while runningthe object

They frequently coincide for a local variable, which is why they get conflated. They are not the same thing, and three situations pull them apart:

An object can exist with no name in scope. A string literal has static storage duration and no name at all. An allocated object — Chapter 9.6 — exists until released, no matter which function is currently running.

A name can be in scope for an object that has ceased to exist. This is the heart of Chapter 9.11's hazard: a pointer variable is still perfectly in scope and perfectly readable after the object it identifies has ended.

A name can be in scope before the object has a usable value. Chapter 2.3's uninitialised read — the object exists, but what it holds is Chapter 9.12's subject.

Chapter 11.5 adds the third question — linkage — and separates all three properly. Keep the first two apart until then.

6. The Pair From Chapter 8.2, Resolved

Azvya Education Pvt. Ltd.VLSI Mentor
one is safe and one is not
    const char *always_safe(void)
    {
        return "constant text";
    }

    const char *never_do_this(void)
    {
        char local[] = "text";
        return local;
    }

always_safe is fine. A string literal has static storage duration, so its lifetime is the whole run of the program. The pointer is still usable after the function returns because the object it identifies has not ended.

never_do_this is broken. local is an array with automatic storage duration. Its lifetime ends when the function's block exits — which is exactly when the pointer is handed to the caller. The caller receives the address of an object that no longer exists.

The compiler catches this specific shape:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: address of stack memory associated with local variable 'local'
    returned [-Wreturn-stack-address]

That warning is in -Wall, so you get it by default. This chapter does not run that program, because using the returned pointer is the undefined behaviour the whole module is about, and Chapter 6.4 established why publishing what happened would teach something false.

Chapter 9.2 is about this case in full, including the versions the compiler does not catch.

7. What You Should Be Able to Say Now

  1. The lifetime of an object is the span of execution during which storage is guaranteed to be reserved for it.
  2. Lifetime is about time, not place. The definition mentions no memory regions.
  3. Using an object outside its lifetime is undefined behaviour — no requirements at all.
  4. Lifetime is decided by storage duration, a property of the declaration.
  5. C17 has automatic, static, allocated and thread; this curriculum teaches the first three.
  6. Every local and parameter so far has been automatic; every string literal has been static.
  7. Automatic lifetime is bounded by the block, not the function — per_pass is 1 on every pass.
  8. Scope and lifetime are different questions: one about names at compile time, one about objects at run time.
  9. always_safe works because a literal is static; never_do_this fails because a local array is automatic.
  10. The bytes are not wiped when a lifetime ends — only the guarantee is withdrawn, which is why the bug survives testing.

8. Exercises

Exercise 1 — name the storage duration. For each, say which storage duration the object has and when its lifetime begins and ends.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int main(void) { int a; ... }
void f(int p) { ... }
const char *s = "text";
void g(void) { for (int i = 0; i < 3; i++) { int t = i; } }

Exercise 2 — predict the output. Do not compile yet, and say how many objects exist over the life of the loop.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for (int k = 0; k < 3; k++) {
    int seen = 0;
    seen = seen + 1;
    printf("%d ", seen);
}
printf("\n");

Exercise 3 — separate the two questions. For each situation, say whether it is a scope issue, a lifetime issue, or both.

  • using a name declared in a block that has already closed
  • holding a pointer to an object whose block has closed
  • using a for loop's control variable after the loop
  • reading a local that was never assigned

Exercise 4 — judge the returns. For each, say whether the returned pointer is usable, and why.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
const char *a(void) { return "literal"; }
char *b(void) { char buf[8] = "text"; return buf; }
int *c(int *caller_object) { return caller_object; }

Exercise 5 — explain the warning. Quote what -Wreturn-stack-address says, explain in terms of storage duration why it is right, and say whether the program would be allowed to build.

Exercise 6 — correct the belief. A colleague says that after a function returns, its locals are "set to garbage, so you can tell when you have used one by mistake." Say what is actually guaranteed, and why their belief makes the bug harder to find rather than easier.

Exercise 7 — reason about the figure. Figure 1 draws three bands of different widths. Say what the width represents, say what the figure deliberately does not show, and explain why Chapter 9.4 rather than this chapter is where that missing picture belongs.

Exercise 8 — find the gap. Of the three storage durations this curriculum teaches, two can be created by writing an ordinary declaration. Say which one cannot, what that implies about the problems it solves, and name the chapter that introduces it.

9. Summary

  • Lifetime is the span of execution during which an object's storage is guaranteed to be reserved.
  • It is about time, not place — the rule mentions no memory regions.
  • Using an object outside its lifetime is undefined behaviour.
  • Lifetime is determined by storage duration: automatic, static, allocated (and thread, not covered).
  • Automatic lifetime is bounded by the block — a loop body creates a new object per iteration.
  • Scope and lifetime are different questions, and Chapter 11.5 adds a third.
  • A literal is static, which is why returning a pointer to one is safe; a local array is automatic, which is why returning a pointer to it is not.
  • -Wreturn-stack-address catches the direct case and is in -Wall.
  • Ending a lifetime does not change the bytes — it withdraws the guarantee, which is what makes the bug survive testing.

The rule is now in place, and the storage duration you have been using without naming it since Module 1 deserves a chapter of its own. Every local, every parameter and every block-scoped object has been automatic, which means every one of them has had a lifetime bounded by a pair of braces — and the single most common way to produce an unusable pointer in C is to hand one of those addresses to something that outlives the braces. The next chapter works through every shape that mistake takes, including the ones the compiler does not warn about.

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.