---
title: 'Resource Cleanup Idioms'
source: 'https://academia.sh/en/courses/c-systems-programming/resource-cleanup-idioms'
course: 'Systems Programming with C'
language: en
updated: '2026-09-26T03:02:44+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Builds a function whose resources must be closed in the reverse of the order they were opened'
    - 'Measures that a resource left half-open on a failure path closes without ever reaching a reader'
    - 'Names the difference between code that releases a resource and code that sees it'
---

# Resource Cleanup Idioms

Measuring a resource that closes without ever reaching a reader, on the failure path of a function that opens several resources in sequence.

This lesson closes the series' final measurement class: **unobserved**. In earlier
lessons, a change either stayed with its writer (copy), became visible to everyone
through a shared object (shared), or passed through a mediator and was read exactly
once (handoff). Here is the fourth case: a resource is opened, and closed by a
failure before it is filled or read — it **never reaches** any reader at all. What is
measured is not where a value reached, but which resource disappeared without ever
reaching anywhere.

## Resources Opened in Order, Closed in Reverse Order

`Store` carries two separate allocations: an `int` array and a label string. Both are
opened in sequence inside `store_open`; if one fails, everything opened up to that
point is closed **in reverse order** — the one opened last is closed first.

```c
// store.h — a struct carrying more than one resource
#ifndef STORE_H
#define STORE_H

struct Store {
    int *values;
    char *label;
    int element_count;
};

int store_open(struct Store *s, int element_count, const char *label);
void store_close(struct Store *s);
int *store_take_values(struct Store *s);

#endif
```

```c
// store.c — sequential opening, reverse-order rollback
#include <stdlib.h>
#include <string.h>
#include "store.h"

int store_open(struct Store *s, int element_count, const char *label) {
    s->values = NULL;
    s->label = NULL;
    s->element_count = 0;

    s->values = malloc((size_t)element_count * sizeof *s->values);
    if (s->values == NULL) {
        return 0;
    }

    s->label = malloc(strlen(label) + 1);
    if (s->label == NULL) {
        free(s->values);
        s->values = NULL;
        return 0;
    }
    strcpy(s->label, label);

    s->element_count = element_count;
    return 1;
}

void store_close(struct Store *s) {
    free(s->label);
    free(s->values);
    s->label = NULL;
    s->values = NULL;
}

int *store_take_values(struct Store *s) {
    int *moved = s->values;
    s->values = NULL;
    s->element_count = 0;
    return moved;
}
```

Opening and closing these two resources gives the same answer to the previous
course's (**Memory and Pointers in C**, K02) question of "who releases it": whoever
calls `store_open` is also responsible for calling `store_close`. This lesson is not
about that contract — it is about how to measure that, when `store_open` stops
halfway, the `label` field was never written to at all.

## A Resource Closing Without Ever Reaching a Reader

The expression `(size_t)element_count * sizeof *s->values` inside `store_open` wraps
around to an enormous value when `element_count` is negative, since it converts to an
unsigned type — no `malloc` can satisfy a size that large, and the call is guaranteed
to return `NULL`. `values` cannot be allocated, `label` is never opened at all, and
`store_open` returns 0.

```c
// failed_open.c — the first resource fails to open, the second never opens at all
#include <stdio.h>
#include "store.h"

int main(void) {
    struct Store s;
    int result = store_open(&s, -1, "measurement");

    printf("%s %d\n", "store_open result", result);
    printf("%s %d\n", "is label NULL because it was never opened", (int)(s.label == NULL));
    printf("%s %d\n", "is values also NULL because it could not be allocated", (int)(s.values == NULL));

    store_close(&s);
    return 0;
}
```

```
store_open result 0
is label NULL because it was never opened 1
is values also NULL because it could not be allocated 1
```

**DS12.** The `-1` given as `element_count` wraps to an enormous value once
`(size_t)element_count` converts it to an unsigned type; multiplying that value by
`sizeof(int)` asks `malloc` for a size no environment can satisfy — the call returns
`NULL`, and `store_open` exits **immediately** in its first `if` branch. `malloc` is
never even called for the `label` field; `s.label` stays at the `NULL` it was assigned
at the top of the function. The `label` resource disappears — without reaching a
reader, without carrying a value — **without ever having existed**. The class is
**unobserved**: the copy, shared, and handoff classes from earlier lessons all assumed
a value reached *somewhere*; here it reaches nowhere at all. `store_close` is still
called regardless — `free(NULL)` is well-defined and harmless, so the cleanup call can
always be made unconditionally.

## Rolling Back the First Resource While the Second Fails to Open

Now suppose the first resource (`values`) opens successfully, but allocation for the
second (`label`) fails — `store_open` would then have to roll the first one back too.
To observe this directly (since a real `malloc` failure cannot be triggered in this
environment), we reproduce `store_open`'s rollback branch as a separate function,
`store_open_second_fails` — the same rollback pattern, with the second allocation
point forced to fail.

```c
// store_open_second_fails.c — rolling back the first resource while the second fails to open
#include <stdlib.h>
#include "store.h"

int store_open_second_fails(struct Store *s, int element_count) {
    s->values = NULL;
    s->label = NULL;
    s->element_count = 0;

    s->values = malloc((size_t)element_count * sizeof *s->values);
    if (s->values == NULL) {
        return 0;
    }

    /* the second allocation is deliberately treated as having failed here */
    free(s->values);
    s->values = NULL;
    return 0;
}
```

```c
// second_rollback.c — the call where the first resource opened is rolled back
#include <stdio.h>
#include "store.h"

int store_open_second_fails(struct Store *s, int element_count);

int main(void) {
    struct Store s;
    int result = store_open_second_fails(&s, 4);

    printf("%s %d\n", "store_open_second_fails result", result);
    printf("%s %d\n", "is the first resource NULL because it was rolled back", (int)(s.values == NULL));
    printf("%s %d\n", "is the second resource NULL because it never opened", (int)(s.label == NULL));

    return 0;
}
```

```
store_open_second_fails result 0
is the first resource NULL because it was rolled back 1
is the second resource NULL because it never opened 1
```

**DS13.** `values` genuinely opens here (`malloc` succeeds for a 4-element array),
but the moment the second resource's opening is treated as having failed, it is
**rolled back** with `free(s->values)`. `s.values` being `NULL` shows that the
resource opened and then closed without ever reaching a reader — much like a single
reading of a handoff value in the Ring Buffers and Queues lesson, except here there is
no reader at all; the value is produced and then destroyed within the same function.
This pattern is exactly what the `goto cleanup` idiom, covered in the previous course's
**Error Handling Patterns** lesson, does: there, closing in reverse order from a
single exit point was built with `goto`; here the same ordering was written by hand
with explicit `if`/`free` calls. This lesson does not re-teach that idiom — it only
measures the result it produces: the resource disappearing unobserved.

## The Order of exit, _exit, and atexit

The rollback so far was built with hand-written `if`/`free` calls. C's `atexit`
function offers another route: it registers a handler to "run when the process
terminates normally." But this registration depends on **how** the process
terminates — the difference between `exit` and `_exit` reveals another face of the
unobserved class here.

```c
// atexit_order.c — exit runs atexit handlers in the reverse of their registration order
#include <stdio.h>
#include <stdlib.h>

static void cleanup_a(void) {
    printf("%s\n", "cleanup_a ran");
}

static void cleanup_b(void) {
    printf("%s\n", "cleanup_b ran");
}

int main(void) {
    atexit(cleanup_a);
    atexit(cleanup_b);

    printf("%s\n", "main's body finished");
    exit(0);
}
```

```
main's body finished
cleanup_b ran
cleanup_a ran
```

```c
// exit_and_atexit_skipped.c — _exit never runs atexit handlers at all
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static void never_runs(void) {
    printf("%s\n", "this line is never printed");
}

int main(void) {
    atexit(never_runs);

    printf("%s\n", "right before calling _exit");
    fflush(stdout);
    _exit(0);
}
```

```
right before calling _exit
```

**DS18.** In the first program, `atexit(cleanup_a)` is registered first and
`atexit(cleanup_b)` second; when `exit(0)` is called, the two run in the **reverse of
their registration order** — `cleanup_b` first, then `cleanup_a`. This repeats
`store_open`'s reverse-order rollback pattern (this lesson's first section), now
automated through `atexit`. In the second program, `never_runs` is likewise
registered with `atexit`, but the process terminates not with `exit` but with
**`_exit`** — POSIX's `_exit` function closes the process immediately, without
running any `atexit` handler at all. Because the registered handler is never called,
`"this line is never printed"` is never printed; the output consists only of the line
before `_exit`. This is the unobserved class at the function level: `never_runs` was
**registered** to reach a reader (an output), but was never run at all. The
`fflush(stdout)` line makes a separate point too: unlike `exit`, `_exit` does not
flush buffered `stdio` output — without that line, the `"right before calling
_exit"` line could have disappeared as well, because the buffer vanishes along with
`_exit` without ever being flushed.

## Handing Off a Resource to Another Variable

`store_close` always kept ownership of `s.values` inside the `Store` struct.
Ownership can also be **handed off** to another variable: `store_take_values` detaches
the array from `s` and hands it to the caller, leaving `s.values` at `NULL` — `s` can
no longer reach that array; access has moved, through a single path, to the new
owner.

```c
// data_handoff.c — ownership of the array inside the store handed off to another variable
#include <stdio.h>
#include <stdlib.h>
#include "store.h"

int main(void) {
    struct Store s;
    store_open(&s, 3, "temporary");

    int *taken = store_take_values(&s);
    taken[0] = 10;

    printf("%s %d\n", "is store.values NULL after the handoff", (int)(s.values == NULL));
    printf("%s %d\n", "value written through taken", taken[0]);

    free(taken);
    store_close(&s);
    return 0;
}
```

```
is store.values NULL after the handoff 1
value written through taken 10
```

**DS20.** Before `store_take_values` is called, the only path to the array is
`s.values`. After the call, `moved` (`taken` on the caller's side) carries the array,
and `s->values` is set to `NULL` — there are never two access paths at the same time;
one closes exactly as the other opens. This differs from this lesson's other
**unobserved** examples: there, the resource disappeared without ever reaching any
reader; here, the resource reaches **exactly one** reader (its new owner) and becomes
unreachable through the old path — the class is **handoff**. After the handoff, the
`free(s.values)` line inside the `store_close(&s)` call is safe: `s.values` is already
`NULL`, and `free(NULL)` does nothing; responsibility for freeing the array now
belongs to whatever code holds `taken`, which is exactly what the separate
`free(taken)` call carries out.

## The Bounding Measurement

The unobserved class measures not that a value reached the *wrong* place, but that it
reached **no place at all**. The checks `s.label == NULL` and `s.values == NULL` are
the proof of this: the resource was disabled without ever being seen by a reader,
assigned, or copied. This is fundamentally different from the previous four lessons'
measurements — copy, shared, and handoff all said "a value reached somewhere, we are
measuring how it got there"; here the question is "a resource disappeared without
reaching anywhere, how do we verify that." The verification tool stays the same:
pointer comparison (`== NULL`), without printing an address or a value.

## Summary

- When several resources are opened in sequence, a failure path must roll them back
  in **reverse order** — the one opened last is closed first.
- When a resource fails to open, that resource never reaches any reader (staying
  `NULL` is the measurable proof of this); when a second resource fails to open, the
  first one already opened is rolled back the same way. This pattern produces the
  same result as the `goto cleanup` idiom in the Error Handling Patterns lesson; this lesson does not teach
  that idiom, only measures its result.
- The class is **unobserved**: what is measured is not where a value reached, but
  which resource disappeared without ever reaching anywhere.
- Handlers registered with `atexit` run in the reverse of their registration order
  when `exit` is called; `_exit` closes the process without running any of them and
  without flushing buffered `stdio` output either.
- When a resource's ownership is handed off to another variable, the old path is set
  to `NULL` and the only access stays with the new owner — the class is **handoff**,
  different from unobserved in that the resource reaches exactly one reader.

## Next Step

These five lessons measured the question "who sees this change" across four classes:
copy, shared, handoff, unobserved. The next topic takes up these classes not one
structure at a time but together — in composite cases where more than one data
structure is used together inside the same function, and figuring out which change
falls into which class has to be worked out by hand.
