---
title: 'Memory and Pointers in C'
source: 'https://academia.sh/en/courses/c-memory'
language: en
updated: '2026-09-26T03:02:42+00:00'
license: 'CC BY-SA 4.0'
lessons: 15
outcomes:
    - 'Measures pointer arithmetic and the relationship between arrays and pointers without printing addresses'
    - 'Tells the owner of a memory block apart as the language, the caller, the callee or nobody'
    - 'Uses allocation and deallocation functions together with an ownership contract'
    - 'Measures a memory leak by a counter difference and prevents it'
    - 'Recognizes the sources of undefined behavior and writes their defined counterpart instead'
---

# Memory and Pointers in C

In C, who will free a block of memory is not written in the source; the information travels by contract and the type system does not check it: the course measures every mechanism with a single question — is this memory owned by the language, the caller, the callee, or nobody? Pointers, pointer arithmetic, dynamic memory, leaks, undefined behavior and bounds checking all pass through the same frame.

## Pointers

Taking an address and accessing the pointed-to value, pointer arithmetic advancing in elements, an array decaying to a pointer, null and dangling pointers, hiding a type behind opaque pointers, and callbacks through function pointers — each together with the question of who owns the memory being pointed to.

### Pointer Basics

Source: https://academia.sh/en/courses/c-memory/pointer-basics

The question of who owns a block of memory; the full run of the five-file measurement core and the 4/4/3/2 distribution across four classes (language, caller, callee, unowned), followed by measuring the address-of and dereference operators without printing an address.

The C Fundamentals course measured which stage (preprocessor, compiler, linker, runtime)
a decision was made at. This course opens with a different question: nothing in the source
says who will **free** a block of memory. The compiler knows a declaration's type, the
linker resolves which definition a name binds to, the runtime only sees the result — but
the answer to "who frees this block" lives in none of them. That answer travels through
a **contract** written in the source itself: a comment in a header file, a function name,
a convention.

The course measures this with a single question: **who owns this memory?** The answer
falls into one of four classes, and these four names hold for the whole course.

#### Four Classes: Language, Caller, Callee, Unowned

| Class | Definition |
|---|---|
| **language** | The language decides the lifetime (automatic or static); nobody calls a free |
| **caller** | Whichever side allocates the memory, or receives it from a function, is obliged to free it |
| **callee** | A module holds the memory; it only lends it to the caller, and cleans up itself on the error path too |
| **unowned** | No side is obliged to free it; the block shows up only as a counter difference |

A five-file measurement core sets up these four classes: `counter.h`/`counter.c` is a thin
wrapper that counts every allocation and every free, `owner.h`/`owner.c` is a unit that
carries an opaque record's ownership contract in its comments, and `ownership.c` is the
measurement function that gathers thirteen observations into a single run. The remaining
fourteen lessons of the course take **slices** of this core; this lesson is the one
exception that runs it in full.

```c
// counter.h — a wrapper that counts every allocation and every free
#ifndef COUNTER_H
#define COUNTER_H

#include <stddef.h>

void *count_alloc(size_t size);
void *count_grow(void *old, size_t size);
void count_free(void *block);
int allocated_count(void);
int freed_count(void);

#endif
```

```c
// counter.c — the counters live only in this unit
#include <stdlib.h>
#include "counter.h"

static int allocated = 0;
static int freed = 0;

void *count_alloc(size_t size) {
    void *block = malloc(size);
    if (block != NULL) {
        allocated++;
    }
    return block;
}

void *count_grow(void *old, size_t size) {
    int new_block = (old == NULL);
    void *grown = realloc(old, size);
    if (grown != NULL && new_block) {
        allocated++;
    }
    return grown;
}

void count_free(void *block) {
    if (block != NULL) {
        freed++;
    }
    free(block);
}

int allocated_count(void) {
    return allocated;
}

int freed_count(void) {
    return freed;
}
```

```c
// owner.h — the record's inside is hidden, the ownership contract is in the comments
#ifndef OWNER_H
#define OWNER_H

#include <stddef.h>

typedef struct Record Record;

/* The caller owns it and frees it with record_destroy. If the name does not
   fit the limit, returns NULL; the function frees what it allocated so far. */
Record *record_create(const char *name, size_t limit);

/* Frees the record together with the name buffer inside it. */
void record_destroy(Record *r);

/* The record owns it: the returned pointer is valid until the record is
   destroyed, and the caller does not free it. */
const char *record_name(const Record *r);

/* The caller owns it: the returned copy is freed with count_free. */
char *record_name_copy(const Record *r);

#endif
```

```c
// owner.c — the unit where the contract is carried out
#include <string.h>
#include "owner.h"
#include "counter.h"

struct Record {
    char *name;
    size_t length;
};

Record *record_create(const char *name, size_t limit) {
    Record *r = count_alloc(sizeof *r);
    if (r == NULL) {
        return NULL;
    }
    r->length = strlen(name);
    if (r->length >= limit) {
        count_free(r);
        return NULL;
    }
    r->name = count_alloc(r->length + 1);
    if (r->name == NULL) {
        count_free(r);
        return NULL;
    }
    memcpy(r->name, name, r->length + 1);
    return r;
}

void record_destroy(Record *r) {
    if (r == NULL) {
        return;
    }
    count_free(r->name);
    count_free(r);
}

const char *record_name(const Record *r) {
    return r->name;
}

char *record_name_copy(const Record *r) {
    char *copy = count_alloc(r->length + 1);
    if (copy != NULL) {
        memcpy(copy, r->name, r->length + 1);
    }
    return copy;
}
```

```c
// ownership.c — the core that measures who owns a piece of memory
#include <stdio.h>
#include <string.h>
#include "owner.h"
#include "counter.h"

static int static_counter = 3;

static int diff(void) {
    return allocated_count() - freed_count();
}

static void observe(const char *name, const char *class, const char *evidence) {
    printf("%-30s%-10s%s\n", name, class, evidence);
}

int main(void) {
    char buffer[64];
    int language = 0, caller = 0, callee = 0, unowned = 0;
    int before, allocated_before, freed_before;

    printf("%-30s%-10s%s\n", "observation", "owner", "evidence");

    /* --- language: the language sets the lifetime, nobody frees --- */

    before = diff();
    int local = 5;
    int *p = &local;
    snprintf(buffer, sizeof buffer, "*p %d, counter diff %d", *p, diff() - before);
    observe("local variable", "language", buffer);
    language++;

    int *s = &static_counter;
    snprintf(buffer, sizeof buffer, "*s %d, no free call", *s);
    observe("static storage", "language", buffer);
    language++;

    const char *literal = "fixed";
    snprintf(buffer, sizeof buffer, "length %d, no free call", (int)strlen(literal));
    observe("string literal", "language", buffer);
    language++;

    int array[4] = {1, 2, 3, 4};
    int *first = array;
    int *last = &array[3];
    snprintf(buffer, sizeof buffer, "last - first %d, *last %d", (int)(last - first), *last);
    observe("local array", "language", buffer);
    language++;

    /* --- caller: whoever allocates frees --- */

    before = diff();
    int *block = count_alloc(4 * sizeof *block);
    int total = 0;
    if (block != NULL) {
        for (int i = 0; i < 4; i++) {
            block[i] = i + 1;
            total += block[i];
        }
    }
    count_free(block);
    snprintf(buffer, sizeof buffer, "total %d, counter diff %d", total, diff() - before);
    observe("allocate and free", "caller", buffer);
    caller++;

    before = diff();
    int *growing = count_grow(NULL, 2 * sizeof *growing);
    int kept = 0;
    if (growing != NULL) {
        growing[0] = 7;
        int *grown = count_grow(growing, 8 * sizeof *grown);
        if (grown != NULL) {
            growing = grown;
        }
        kept = growing[0];
    }
    count_free(growing);
    snprintf(buffer, sizeof buffer, "first element %d, counter diff %d", kept, diff() - before);
    observe("resize", "caller", buffer);
    caller++;

    allocated_before = allocated_count();
    Record *r = record_create("ledger", 16);
    snprintf(buffer, sizeof buffer, "not null %d, allocated blocks %d", r != NULL, allocated_count() - allocated_before);
    observe("opaque record", "caller", buffer);
    caller++;

    before = diff();
    char *copy = record_name_copy(r);
    snprintf(buffer, sizeof buffer, "copy %s", copy != NULL ? copy : "none");
    count_free(copy);
    snprintf(buffer + strlen(buffer), sizeof buffer - strlen(buffer), ", counter diff %d", diff() - before);
    observe("returned copy", "caller", buffer);
    caller++;

    /* --- callee: the module owns it, the caller does not free --- */

    before = diff();
    const char *lent = record_name(r);
    snprintf(buffer, sizeof buffer, "name %s, counter diff %d", lent, diff() - before);
    observe("lent name", "callee", buffer);
    callee++;

    before = diff();
    Record *too_long = record_create("a very long record name", 8);
    snprintf(buffer, sizeof buffer, "null %d, counter diff %d", too_long == NULL, diff() - before);
    observe("cleanup on error path", "callee", buffer);
    callee++;

    freed_before = freed_count();
    record_destroy(r);
    snprintf(buffer, sizeof buffer, "freed blocks %d", freed_count() - freed_before);
    observe("record's inner buffer", "callee", buffer);
    callee++;

    /* --- unowned: nobody is obliged to free it --- */

    before = diff();
    int *lost = count_alloc(sizeof *lost);
    lost = count_alloc(sizeof *lost);
    count_free(lost);
    snprintf(buffer, sizeof buffer, "counter diff %d", diff() - before);
    observe("overwritten pointer", "unowned", buffer);
    unowned++;

    Record *r2 = record_create("draft", 16);
    before = diff();
    if (r2 != NULL) {
        record_name_copy(r2);
    }
    snprintf(buffer, sizeof buffer, "counter diff %d", diff() - before);
    record_destroy(r2);
    observe("ignored return", "unowned", buffer);
    unowned++;

    printf("\n%-10s%d\n", "language", language);
    printf("%-10s%d\n", "caller", caller);
    printf("%-10s%d\n", "callee", callee);
    printf("%-10s%d\n", "unowned", unowned);
    printf("total observations: %d\n", language + caller + callee + unowned);
    printf("allocated %d freed %d diff %d\n", allocated_count(), freed_count(), diff());
    return 0;
}
```

```
observation                   owner     evidence
local variable                language  *p 5, counter diff 0
static storage                language  *s 3, no free call
string literal                language  length 5, no free call
local array                   language  last - first 3, *last 4
allocate and free             caller    total 10, counter diff 0
resize                        caller    first element 7, counter diff 0
opaque record                 caller    not null 1, allocated blocks 2
returned copy                 caller    copy ledger, counter diff 0
lent name                     callee    name ledger, counter diff 0
cleanup on error path         callee    null 1, counter diff 0
record's inner buffer         callee    freed blocks 2
overwritten pointer           unowned   counter diff 1
ignored return                unowned   counter diff 1

language  4
caller    4
callee    3
unowned   2
total observations: 13
allocated 11 freed 9 diff 2
```

**PO1.** Of the thirteen observations, **4** landed in the `language` class, **4** in
`caller`, **3** in `callee`, and **2** in `unowned`. None of the four `language` rows call
a free — the local variable, the static storage, the string literal, and the local array
each end their own lifetime. The `allocated 11 freed 9 diff 2` line says that at the end
of the run **two** blocks are still left unfreed; this matches exactly the gap left by the
`unowned` rows — the overwritten pointer and the ignored return each lost a whole block.
This run compiled **without warnings** and produced the **exact same** output across three
separate runs; every remaining lesson in the course takes this table and these four classes
as its source.

#### Address-Of and Dereference: Deepening the `local variable` Row

The core's first row — `local variable` — contains the most common pair of operators:
address-of (`&`) and dereference (`*`). This lesson measures them separately, outside the
core, with a small run that focuses on nothing else.

```c
// address_of.c — address-of and dereference, measured with equality and value, no address printed
#include <stdio.h>

int main(void) {
    int number = 42;
    int *p = &number;

    printf("%s %d\n", "pointed-to value", *p);
    printf("%s %d\n", "does p point at number", (int)(p == &number));

    *p = 100;
    printf("%s %d\n", "new value written through dereference", number);

    int other = 7;
    p = &other;
    printf("%s %d\n", "pointed-to value after reassignment", *p);
    printf("%s %d\n", "does p point at number now", (int)(p == &number));
    return 0;
}
```

```
pointed-to value 42
does p point at number 1
new value written through dereference 100
pointed-to value after reassignment 7
does p point at number now 0
```

**PO2.** The expression `&number` produces the address of the `number` variable, and that
address is assigned to `p`; the comparison `p == &number` gives **1**, meaning `p` really
does point at `number` — but this comparison never prints the address itself, it only asks
whether two addresses are equal. The line `*p = 100` uses the **dereference operator** on
the left side of an assignment: it writes to where `p` points, not to `p` itself. The proof
is the next line — `number` is now **100**; the write made through `p` really did change
the variable it points to. With `p = &other`, `p` can be **reassigned** to point at a
different variable; after this assignment `*p` gives **7**, and `p == &number` is now
**0** — `p` is itself a variable, free to hold a different address whenever it is
reassigned.

**PO3.** The declaration `int *p` says that `p` holds **the address of an `int`** — but
that type never says how the memory `p` points to was obtained. In the run above, `p`
first pointed at one local variable (`number`), then at another (`other`); both were in
the `language` class, and nobody freed either. But the `int *` type can just as well point
at a dynamically allocated block — the core's `allocate and free` row did exactly that:
`int *block = count_alloc(...)`. Same declaration, same operators (`*`, `&`), two different
ownership classes. This is the lesson's bounding measurement.

#### Pointer to Pointer: A Second Layer of Dereference

The `p` variable above pointed at an `int`; but since a pointer is itself a variable, another
pointer can be written that holds its address. This takes two `*` instead of one, and
deepens the dereference by one more layer.

```c
// double_pointer.c — a pointer to a pointer, applying dereference one layer deeper
#include <stdio.h>

int main(void) {
    int number = 9;
    int *p = &number;
    int **pp = &p;

    printf("%s %d\n", "value read through double dereference", **pp);
    printf("%s %d\n", "does pp point at p's address", (int)(*pp == p));

    **pp = 30;
    printf("%s %d\n", "new value written through double dereference", number);

    int other = 4;
    *pp = &other;
    printf("%s %d\n", "value p points to after changing p through pp", *p);
    printf("%s %d\n", "does p point at number now", (int)(p == &number));
    return 0;
}
```

```
value read through double dereference 9
does pp point at p's address 1
new value written through double dereference 30
value p points to after changing p through pp 4
does p point at number now 0
```

**PO4.** The declaration `int **pp` says that `pp` holds the address of an `int *` —
`pp` points at `p`, and `p` points at `number`. `**pp` applies two dereference operators in
sequence: first `*pp` reaches the address carried by `p`, then the second `*` reaches the
value at that address. The line `**pp = 30` uses this two-layer path on the left side of an
assignment, and `number` really does become **30** — just as `*p = 100` changed `number`
earlier, but now with `p` standing in between. The last two lines show that `*pp`, meaning
`p` itself, can be reassigned: the assignment `*pp = &other` turns `p` into a pointer at
`other` — through `pp`, without touching `p` directly. This distinction matters whenever
a function needs to change the caller's own pointer: a function taking `int *` can only
change the pointed-to value, while a function taking `int **` can turn the caller's pointer
toward a different address. The class is still `language`: all three are local variables,
none of them allocated.

#### The Bounding Measurement

A pointer's type does not say **who will free** the block it points to. An `int *` can
point at a local variable just as well as at a block allocated with `count_alloc` — in the
source the two look identical, and the compiler draws no distinction between them. In the
two runs above, the `p` variable pointed at three different things at three different
moments: one local variable, another local variable, and (in the core) an allocated block.
The declaration was the same all three times (`int *`). Someone reading a pointer **cannot
infer** its ownership class from its type alone — that information can only be learned by
looking at where the variable came from (a `&` expression or an allocation call), or by
reading the contract that documents it. The rest of the course measures how that contract
is written, and how it is broken.

#### Summary

- The owner of a memory block is one of four classes: **language**, **caller**, **callee**,
  **unowned**; this lesson measures the **language** class with a full run of the core.
- The five-file measurement core (`counter.h`, `counter.c`, `owner.h`, `owner.c`,
  `ownership.c`) distributes thirteen observations as **4 language · 4 caller · 3 callee ·
  2 unowned**; the total diff is **2**.
- Address-of (`&`) produces a variable's address; dereference (`*`) reads or writes the
  value at that address — both can be measured with equality and value, without ever
  printing the address.
- A pointer can be reassigned and can point at different variables at different times;
  that is the difference between the pointer itself and the value it points to.
- A pointer's type does not say **who will free** the block it points to — the same
  `int *` declaration can point at either a local variable or an allocated block.

#### Next Step

The `local array` row measured in this lesson gave a `last - first` difference of **3**,
but how that difference is computed was left unexamined. The next lesson, Pointer
Arithmetic, measures why that difference is counted in **elements** rather than bytes, and
how incrementing a pointer advances according to its type's size — still within the
`language` class, but now looking at the relationship between an array's elements rather
than a single value.

### Pointer Arithmetic

Source: https://academia.sh/en/courses/c-memory/pointer-arithmetic

Why incrementing a pointer, decrementing it, and subtracting two pointers operates in elements rather than bytes; why an arithmetic that runs past the bounds is never run.

The previous lesson's `local array` row gave a `last - first` difference of **3**: in a
four-element array, there are three steps between the first and last elements. But that
difference is not in bytes — the actual memory distance between `last` and `first` is a
multiple of however many bytes the array's element type takes up. This lesson measures
where that multiplier comes from, and what it means to increment and decrement a pointer.
The class is still **language**: moving between an array's elements requires no allocation
or free call at all.

#### Increment and Decrement: Advancing by Type Size

```c
// increment.c — incrementing a pointer advances by its type's size
#include <stdio.h>

int main(void) {
    int array[5] = {10, 20, 30, 40, 50};
    int *p = array;

    printf("%s %d\n", "first element", *p);
    p++;
    printf("%s %d\n", "element after one increment", *p);
    p += 2;
    printf("%s %d\n", "element after three increments", *p);
    p--;
    printf("%s %d\n", "element after one decrement", *p);

    double pair[3] = {1.5, 2.5, 3.5};
    double *d = pair;
    d++;
    printf("%s %.1f\n", "element after one increment on a double array", *d);
    return 0;
}
```

```
first element 10
element after one increment 20
element after three increments 40
element after one decrement 30
element after one increment on a double array 2.5
```

**PO5.** `p++` moves the pointer to the next `int` — the step written is always **one
element**, never one byte. `p += 2` skips two elements at once: from index 1 to index 3.
`p--` steps back by one element. The same `p++` spelling was used on the `pair` array of
`double` values, and it again advanced by exactly **one element** — even though `int` and
`double` have different sizes, the increment operator means "the next element" for both.
The size itself (`sizeof`) was never printed anywhere; the measurement was made only
through the pointed-to **value**.

#### The Difference and Order of Two Pointers

```c
// diff_and_order.c — the difference between two pointers is in elements, their order in comparison
#include <stdio.h>

int main(void) {
    int array[6] = {1, 2, 3, 4, 5, 6};
    int *first = &array[0];
    int *middle = &array[3];
    int *last = &array[5];

    printf("%s %d\n", "middle minus first", (int)(middle - first));
    printf("%s %d\n", "last minus middle", (int)(last - middle));
    printf("%s %d\n", "does first come before middle", (int)(first < middle));
    printf("%s %d\n", "does last come before middle", (int)(last < middle));

    int *walker = first;
    int steps = 0;
    while (walker < last) {
        walker++;
        steps++;
    }
    printf("%s %d\n", "steps taken from first to last", steps);
    return 0;
}
```

```
middle minus first 3
last minus middle 2
does first come before middle 1
does last come before middle 0
steps taken from first to last 5
```

**PO6.** `middle - first` gives **3**: there is a gap of three elements between `array[3]`
and `array[0]`. The result of this subtraction is always the **element count**, never the
byte difference between two addresses — the expression `middle - first` prints `3`, and the
addresses themselves are never seen. The comparison `first < middle` gives **1** because
`first` sits before `middle` in the array; this ordering comes from the `<` operator being
defined for two pointers within the same array. The loop built with the condition
`walker < last` advances exactly **5** steps from `first` to `last` — which matches the
`last - first` difference exactly.

#### Arithmetic on a `char` Array: When the Element Step Equals the Byte Step

The previous two examples used `int` and `double` arrays; in both, a single increment
advanced by more than one byte, but that advance was never measured directly — only the
pointed-to value was read. The `char` type creates a special case here: `sizeof(char)` is
always **1**, so incrementing a `char *` covers a step of one element and a step of one
byte at the same time. This case comes up often when moving through a string's characters.

```c
// character_walk.c — one increment on a char pointer moves to the next character
#include <stdio.h>

int main(void) {
    char string[] = "abcd";
    char *letter = string;

    printf("%s %c\n", "first character", *letter);
    letter++;
    printf("%s %c\n", "character after one increment", *letter);

    char *last_letter = &string[3];
    printf("%s %d\n", "last letter minus first letter", (int)(last_letter - string));
    printf("%s %c\n", "value of the last character", *last_letter);
    return 0;
}
```

```
first character a
character after one increment b
last letter minus first letter 3
value of the last character d
```

**PO7.** The call `letter++` moves from `string[0]` to `string[1]`; the value read changes
from `'a'` to `'b'`. The difference `last_letter - string` gives **3** — `string[3]` is
three elements from the start of the array. This difference follows the same rule as the
`middle - first` example in the `int` array: the result is in elements. That the difference
also happens to equal a byte distance for `char` is a direct consequence of `sizeof(char)`
being **1** — not an exception to the rule, but the shape the rule itself takes for `char`.

#### Row Stepping in a Two-Dimensional Array

The three examples so far used one-dimensional arrays; the increment always advanced by
**one element**. In a two-dimensional array this rule does not change, but the definition
of "element" does: when a row pointer (`int (*)[4]`) is defined over `int matrix[3][4]`,
an increment skips not a single `int` but **an entire row** (4 `int`s).

```c
// row_step.c — a row pointer in a two-dimensional array counts a whole row as one step
#include <stdio.h>

int main(void) {
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    int (*row)[4] = matrix;

    printf("%s %d\n", "first element of the first row", row[0][0]);
    row++;
    printf("%s %d\n", "first element of the row after one increment", row[0][0]);
    printf("%s %d\n", "third element of the same row", row[0][2]);

    int *flat = &matrix[0][0];
    int *second_row_start = &matrix[1][0];
    printf("%s %d\n", "second row start minus first element", (int)(second_row_start - flat));
    return 0;
}
```

```
first element of the first row 1
first element of the row after one increment 5
third element of the same row 7
second row start minus first element 4
```

**PO8.** The `row` variable has type `int (*)[4]` — it carries the type "address of an
array of four `int`s," not a plain `int *`. When `row++` is called, the pointer moves to
the next **row**: the value of `row[0][0]` jumps from `1` to `5`, that is, to the start of
`matrix`'s second row. This is the same rule as `p++` advancing by one `int` in
`increment.c`, seen on a different type: the step is always the size of the **pointed-to
type**, and here that type is not a single `int` but a row of four `int`s. The difference
between a plain `int *` (`flat`) and the start of the second row comes out to **4** — which
confirms why `int matrix[3][4]` decays to `int (*)[4]` rather than `int **` when passed to
a function: the row size (4) travels as part of the type, otherwise `row++` could not
advance to the correct element.

#### The Same Rule on a Dynamically Allocated Array

The three examples so far ran on local (`language` class) arrays. The same arithmetic
rules hold **exactly** on a dynamically allocated block as well — advancing by type size
does not require the pointer to know where the array came from.

```c
// dynamic_increment.c — pointer arithmetic on a dynamically allocated block follows the same rule
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int length = 4;
    int *array = malloc((size_t)length * sizeof *array);
    if (array == NULL) {
        printf("%s\n", "allocation failed");
        return 1;
    }
    for (int i = 0; i < length; i++) {
        array[i] = (i + 1) * 10;
    }

    int *p = array;
    printf("%s %d\n", "first element", *p);
    p += 2;
    printf("%s %d\n", "element after two increments", *p);

    int *last = array + length - 1;
    printf("%s %d\n", "last minus first", (int)(last - array));

    free(array);
    array = NULL;
    return 0;
}
```

```
first element 10
element after two increments 30
last minus first 3
```

**PO9.** `array` is a block allocated with `malloc`, but the call `p += 2` produces
**exactly the same result** as the local array in `increment.c`: it skips two elements,
from `10` to `30`. The difference `last - array` is **3** — the same rule as the
`middle - first` example in `diff_and_order.c`. The difference is not in the arithmetic but
in ownership: this block was requested with `malloc`, so its class here is **caller** —
`main` allocates `array`, `main` uses it, and the obligation to free it stays with `main`
as well. The arithmetic itself is entirely independent of that ownership fact: whether the
block a pointer points to is a local array in the `language` class or a dynamic block in
the `caller` class, how far `p++` advances depends only on the **type**, never on where the
memory came from.

#### The Equivalence Between Indexing and Arithmetic

So far, `p[i]` notation and `p + i` notation have each been used separately, but the
relationship between them was never measured. In C, square-bracket indexing is a
**shorthand** defined in terms of arithmetic: the expression `p[i]` means exactly
`*(p + i)`, neither a byte off nor a separate rule.

```c
// index_equivalence.c — p[i] notation and *(p + i) notation point at the same address
#include <stdio.h>

static int index_it(int *p, int i) {
    return p[i];
}

static int add_by_dereference(int *p, int i) {
    return *(p + i);
}

int main(void) {
    int array[5] = {100, 200, 300, 400, 500};
    int *p = array;

    int index_result = index_it(p, 3);
    int arithmetic_result = add_by_dereference(p, 3);
    printf("%s %d\n", "p indexed at three", index_result);
    printf("%s %d\n", "p advanced three times and dereferenced", arithmetic_result);
    printf("%s %d\n", "do the two notations give the same value", (int)(index_result == arithmetic_result));

    int zeroth_index = index_it(p, 0);
    int zeroth_arithmetic = add_by_dereference(p, 0);
    printf("%s %d\n", "zeroth index", zeroth_index);
    printf("%s %d\n", "same as dereferenced p", (int)(zeroth_index == zeroth_arithmetic));
    return 0;
}
```

```
p indexed at three 400
p advanced three times and dereferenced 400
do the two notations give the same value 1
zeroth index 100
same as dereferenced p 1
```

**PO10.** The expressions `p[3]` and `*(p + 3)` give the same value (**400**), and the
equality comparison comes out **1** — confirming that the two notations express exactly
the same operation to the compiler. The equality between `p[0]` and `*p` is an edge case of
the same rule: the zeroth index is identical to dereferencing directly, with no advance at
all. This equivalence explains why all of this lesson's `p++`, `p += n`, and `last - first`
measurements gave results consistent with `array[i]` notation: indexing is a notational
convenience built on top of arithmetic, not a separate mechanism. The class is still
**language**: both `p[3]` and `*(p + 3)` point at the same local array, and the only
difference between them is notation. This equivalence also explains something else: even
`array[i]` notation is itself a form of pointer arithmetic, because `array` decays to the
address of its first element whenever it enters a function or an operator context, and
`array[i]` becomes exactly equivalent to `*(array + i)`.

#### The Bounding Measurement

Pointer arithmetic is defined only **within the same array, or one element past its end**
— pointing one past the array's last element (for loop termination) is valid, but
**dereferencing** that address, or advancing the array by two elements past it, is
undefined behavior. In the `diff_and_order.c` example above, `walker` advances during the
loop only as far as the address `last` and stops there — an increment that runs past the
array's bound is never run. This bound is not something the compiler checks: writing
`p += 100` compiles too, but its result is undefined, and this lesson never includes such
a call. The validity of the arithmetic depends on the person writing the code knowing the
array's bounds — the type system does not carry that bound.

The same bound applies to pointer values that fall **outside** a statically allocated
block such as `array`: pointing to one before the start of the array with `p - 1`, or going
further than `last + 1`, falls into the same class of undefined behavior. This is exactly
why the bound is defined as "one element past the end": the standard guarantees just
enough that a loop termination condition (like `walker < last`) can safely reach that one
address, and no more.

This bound also holds between pointers belonging to two **unrelated** arrays: if `int a[4]`
and `int b[4]` are two separately defined local arrays, `&b[0] - &a[0]` compiles but its
result is **undefined** — the subtraction operator only guarantees a meaningful element
count for two pointers within the same array (or one element past it); where two different
arrays sit relative to each other in memory is a detail left to the compiler's decision,
and a program cannot rely on it. Both the `diff_and_order.c` and `dynamic_increment.c`
examples above observe this rule indirectly: both take a difference only between two
pointers **within a single array or a single block**, never comparing two unrelated blocks
like `a` and `b`.

#### Summary

- Pointer increment and decrement (`p++`, `p--`, `p += n`) always advance by **the type's
  size**; the step count is in elements, never in bytes.
- The difference between two pointers (`last - first`) gives the **element count** between
  them; the address itself is never printed.
- Two pointers can be **ordered** with comparison operators like `<` and `>` — within the
  same array, that ordering matches their position order in the array.
- **Pointing** one past an array's bound is valid, but **dereferencing** that address is
  undefined behavior; taking the difference between pointers of two unrelated arrays falls
  into the same class — neither was run in this lesson.
- `p[i]` notation is a shorthand for `*(p + i)`; the two notations always point at the same
  address and return the same value.
- The arithmetic rules do not change whether the block a pointer points to is a local
  array (`language`) or a dynamically allocated block (`caller`); the step depends only on
  the type, never on the memory's source. This is why the memory this lesson measures is
  in the **language** class: the rule is set by the language itself, not by the memory's
  owner.

#### Next Step

An array's name decays on its own into the address of its first element in most contexts —
the `int *first = array` notation already used this without dwelling on it. The next
lesson, Arrays and Pointers, measures when this decay happens, how an array is passed to a
function (and why without length information), and the first step into the **caller**
class: the ownership of a dynamically allocated array.

### Arrays and Pointers

Source: https://academia.sh/en/courses/c-memory/arrays-and-pointers

How an array's name decays into the address of its first element when it is passed to a function; why that decay loses the length information; and how ownership of a dynamically allocated array passes to the caller.

Two lessons ago, the core's `allocate and free` row allocated a block with `count_alloc`
and freed it with `count_free`; its class was **caller**, because the side requesting the
memory was obligated to free it. This lesson deepens that row, first by measuring how
array names reach functions — because freeing a dynamic array correctly first requires
knowing **what** it arrives at a function **as**.

#### Decay: When an Array Name Is Passed to a Function

```c
// decay.c — an array name decays into the address of its first element in a function parameter
#include <stdio.h>

static void show(int *p, int length) {
    int total = 0;
    for (int i = 0; i < length; i++) {
        total += p[i];
    }
    printf("%s %d\n", "total inside function", total);
    printf("%s %d\n", "first element inside function", *p);
}

int main(void) {
    int array[4] = {2, 4, 6, 8};

    printf("%s %d\n", "first element outside array", *array);
    printf("%s %d\n", "does array equal the first element's address", (int)(array == &array[0]));

    show(array, 4);

    int *pointer = array;
    printf("%s %d\n", "does the array assigned to a pointer equal the first element", (int)(pointer == &array[0]));
    return 0;
}
```

```
first element outside array 2
does array equal the first element's address 1
total inside function 20
first element inside function 2
does the array assigned to a pointer equal the first element 1
```

**PO11.** The comparison `array == &array[0]` gives **1**: when an array name is used, that
name carries **the same value as the address of its first element**. In the call
`show(array, 4)`, `array` passes to the function's `int *p` parameter as this address — the
notation `p[i]` inside the function reaches the same elements as `array[i]`, and the total
comes out **20** (2+4+6+8). The assignment `pointer = array` shows the same thing: when an
array name is assigned to a pointer variable, it is not the array itself that gets copied,
but the address of its first element. This is called **decay**: an array name loses its
own type (array) in a function parameter or a pointer assignment and turns into a pointer
type.

#### The Lost Length

```c
// length_loss.c — decay does not carry the array's element count
#include <stdio.h>

static int sum(int *p, int length) {
    int total = 0;
    for (int i = 0; i < length; i++) {
        total += p[i];
    }
    return total;
}

int main(void) {
    int small[3] = {1, 1, 1};
    int large[7] = {1, 1, 1, 1, 1, 1, 1};

    printf("%s %d\n", "small array total", sum(small, 3));
    printf("%s %d\n", "large array total", sum(large, 7));
    printf("%s %d\n", "large array total with wrong length", sum(large, 3));
    return 0;
}
```

```
small array total 3
large array total 7
large array total with wrong length 3
```

**PO12.** The `sum` function can **never** work out how many elements long the array is
from the pointer it receives — this is why `length` is carried as a separate parameter. In
the last call, the `large` array (7 elements) passes to the function, but `length` is given
as **3**; the function trusts this wrong information and only sums the first three
elements, giving a result of **3** — with no relation to the array's actual size at all.
This is the bounding consequence of decay: when an array is passed to a function, the
length information must be **carried by hand** as a separate value at the source; it can
never be recovered from the pointer itself.

Carrying the length as a separate parameter is not the only method used in C. Another
common solution is to place a **terminator value** inside the array itself — the `'\0'`
at the end of a string does exactly this job: a loop walking over a string learns the
length not from a separate parameter, but by advancing until it reaches the terminator.
What the two methods have in common: the information lost through decay must be recovered
**some other way** at the source — either carried alongside as a number, or found through a
mark embedded inside the array. There is no third way; the pointer itself never carries
this information.

#### Ownership of a Dynamic Array

```c
// dynamic_array.c — the owner of an allocated array is the side that requested it
#include <stdio.h>
#include <stdlib.h>

static int *create_array(int length, int start) {
    int *array = malloc((size_t)length * sizeof *array);
    if (array == NULL) {
        return NULL;
    }
    for (int i = 0; i < length; i++) {
        array[i] = start + i;
    }
    return array;
}

int main(void) {
    int length = 5;
    int *array = create_array(length, 10);

    if (array == NULL) {
        printf("%s\n", "allocation failed");
        return 1;
    }

    int total = 0;
    for (int i = 0; i < length; i++) {
        total += array[i];
    }
    printf("%s %d\n", "dynamic array total", total);
    printf("%s %d\n", "last element", array[length - 1]);

    free(array);
    array = NULL;
    printf("%s %d\n", "is it a null pointer after being freed", (int)(array == NULL));
    return 0;
}
```

```
dynamic array total 60
last element 14
is it a null pointer after being freed 1
```

**PO13.** `create_array` **allocates** an array but never **frees** it itself — it returns
the block to `main`. The total inside `main` is **60** (10+11+12+13+14) and the last
element is **14**, showing the array filled correctly. Ownership crosses the function
boundary here: `create_array` allocates the memory, but `main`, which **requested** and
uses it, is obligated to free it — which is why the class is **caller**. Right after the
`free(array)` call, `array` is assigned `NULL`, and the comparison `array == NULL` gives
**1** — this prevents the dangerous condition, the topic of the next lesson, of a freed
pointer.

#### How Far a Wrong Length Stays Silent

The `length_loss.c` example showed a wrong length giving an incomplete total, but that was
only the "undercounting" case. What happens when the wrong length is a value that **exceeds
the array's actual bound**? The following example measures this question by placing a call
that stays within bounds and a call that exceeds it side by side.

```c
// bound_overrun.c — a correct length stays within bounds, a wrong length can exceed it
#include <stdio.h>

static int largest(const int *p, int length) {
    int large = p[0];
    for (int i = 1; i < length; i++) {
        if (p[i] > large) {
            large = p[i];
        }
    }
    return large;
}

int main(void) {
    int array[4] = {3, 2, 9, 7};

    printf("%s %d\n", "largest with correct length", largest(array, 4));
    printf("%s %d\n", "largest with a short length", largest(array, 2));
    return 0;
}
```

```
largest with correct length 9
largest with a short length 3
```

**PO14.** Called with the correct length (`4`), `largest` scans the whole array and finds
**9**. Called with a short length (`2`), it only looks at the first two elements and
returns **3** — the value `9` exists in the array but is never seen because it sits third.
This lesson deliberately includes only the **short** length call; giving a value that
**exceeds** the actual length (something like `largest(array, 6)`) would try to read memory
past the end of the array, and that is undefined behavior — so it is never run here. What
the two cases have in common: the `largest` function trusts the `length` value passed to it
**unconditionally** — it can detect neither a short nor a long value on its own, because
all it has is a starting address and a number.

#### Returning the Length Along With It

`create_array` took the length as a value `main` already knew, and never computed it
itself. If a function **determines the length of the array it allocates itself** — say, it
produces a filtering result, an array shorter than its input — how does it report that
length back to the caller? Since the pointer itself does not carry it, the only way is an
**output parameter**.

```c
// returns_length_too.c — an allocating function reports the length back through an output parameter
#include <stdio.h>
#include <stdlib.h>

static int *select_evens(const int *source, int length, int *result_length) {
    int *result = malloc((size_t)length * sizeof *result);
    if (result == NULL) {
        *result_length = 0;
        return NULL;
    }
    int count = 0;
    for (int i = 0; i < length; i++) {
        if (source[i] % 2 == 0) {
            result[count] = source[i];
            count++;
        }
    }
    *result_length = count;
    return result;
}

int main(void) {
    int source[6] = {1, 2, 3, 4, 5, 6};
    int length = 0;

    int *evens = select_evens(source, 6, &length);
    printf("%s %d\n", "number of elements selected", length);

    int total = 0;
    for (int i = 0; i < length; i++) {
        total += evens[i];
    }
    printf("%s %d\n", "total of the selected elements", total);

    free(evens);
    evens = NULL;
    return 0;
}
```

```
number of elements selected 3
total of the selected elements 12
```

**PO15.** `select_evens` knows the source array can produce **at most** `length` elements,
so it allocates the block at that size, but it only learns how many of them are actually
even after scanning — it **writes this information back** to the caller through
`*result_length`. After this call, `main` holds both the new array (`evens`) and its actual
length (`length`, now **3**); the total, **12** (2+4+6), proves the two match up correctly.
Ownership is again with the **caller**, just as in the `create_array` example:
`select_evens` allocates the memory but never frees it, `main` uses it and frees it with
`free`. Unlike `create_array`, this example measures the case where the length comes not
from the **input** but from **the function's own count** — so the length is no longer an
input parameter but an output parameter. `result_length` itself passes as an `int *` — a
separate usage showing that pointers can be used to write a single value "back to the
caller," not only to reach arrays.

#### Decay of a Two-Dimensional Array

A one-dimensional array decayed into `int *` when passed to a function. A two-dimensional
array's decay works **one level short**: `int matrix[3][4]` does not become `int **` in a
function parameter — the row size (4 elements) stays as part of the type, and it decays
into `int (*)[4]`.

```c
// matrix_decay.c — int matrix[3][4] passes to a function as int (*)[4], not int **
#include <stdio.h>

static int row_total(int row[4]) {
    int total = 0;
    for (int i = 0; i < 4; i++) {
        total += row[i];
    }
    return total;
}

static int grand_total(int (*matrix)[4], int row_count) {
    int total = 0;
    for (int i = 0; i < row_count; i++) {
        total += row_total(matrix[i]);
    }
    return total;
}

int main(void) {
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    printf("%s %d\n", "first row total", row_total(matrix[0]));
    printf("%s %d\n", "grand total of the matrix", grand_total(matrix, 3));
    return 0;
}
```

```
first row total 10
grand total of the matrix 78
```

**PO16.** The `grand_total` function's parameter is `int (*matrix)[4]` — it carries the
type "the address of an array of four elements." The notation `matrix[i]` here means
`*(matrix + i)`, and it gives the address of the first element of the `i`th **row**; that
address decays once more (this time to `int *`) when it passes to `row_total`. The
`first row total` is **10** (1+2+3+4), and the `grand total of the matrix` is **78** (the
sum of all elements from 1 to 12) — proving the two-layer decay reaches the correct
elements. If the parameter had been written as `int **matrix`, the compiler would
**reject** it: `int (*)[4]` and `int **` are the same size (one address) but are different
types, because the first carries the information "the address of an array whose row size
is 4," while the second means only "a pointer to a pointer" — this lost information is
exactly what the compiler needs to route a `matrix[i][j]` access to the correct row.

#### The Bounding Measurement

Decay only operates at **a single level**: an `int array[4]` becomes `int *` when passed to
a function, but an `int matrix[3][4]` does **not** become `int **` — the `matrix_decay.c`
example above already measured this through row totals. This lesson stops at
multi-dimensional arrays here; the rule that gets fixed is: decay always produces **an
address**, and that address never carries the information of "how many elements a block it
points to." This information must always be carried explicitly at the source, either
through a separate parameter (like `length` above) or a terminator value (like the `'\0'`
at the end of a string). In `matrix_decay.c`, this information traveled by a third route:
because the row size (**4**) stays embedded in the type itself (`int (*)[4]`), no separate
parameter was needed for it, but the **row count** (**3**) still had to be passed to
`grand_total` as a separate `row_count` parameter — because the type only carries the
column size, not the row count.

#### Summary

- An array name **decays** into the address of its first element when passed as a
  function parameter or assigned to a pointer; `array == &array[0]` proves this decay.
- Decay **does not carry the array's element count** — given a wrong length parameter, a
  function cannot detect it; it only accesses however many elements it was given.
- The owner of a dynamically allocated array is **the side that requested and uses it**
  (class **caller**); the allocating function can hand the block back and pass on the
  obligation to free it.
- Assigning `NULL` to a pointer right after freeing a block explicitly marks at the source
  that the pointer no longer points to a valid block.
- The length of a multi-element block **cannot be recovered** from the pointer itself; it
  must be carried by a separate value, an output parameter, or a terminator — an input
  parameter if the length comes from the input, an output parameter if the function
  computes it itself, but ownership stays with the **caller** in both cases.
- A two-dimensional array decays into type `int (*)[N]` when passed to a function, not
  `int **` — the row size is carried as part of the type, the row count is not.

The three new measurements in this lesson all tie back to the same observation from
different angles: an array's decay into a pointer carries no information about the array
**itself** — not its length, not its row count, not how far it is safe to read. This
information always has to come through a separate channel: either as a parameter
(`length` in `largest`), embedded in the type itself (`int (*)[4]` in `matrix_decay.c`), or
as an output parameter (`result_length` in `returns_length_too.c`). All three are
different solutions to the same bound; none removes the bound, they only make it visible
and portable.

#### Next Step

In the previous example, `array` was assigned `NULL` right after the `free(array)` call —
but what happens if that step is skipped? The next lesson, Null and Dangling Pointers,
measures what a pointer that still points at a freed block means, why that condition is
never run here, and why assigning `NULL` moves it out of the **unowned** class into a
condition that can be checked.

### Null and Dangling Pointers

Source: https://academia.sh/en/courses/c-memory/null-and-dangling-pointers

Why a null pointer is a checkable condition, while a dangling pointer keeps pointing at a freed block and is therefore never run; how both are two faces of the unowned class.

The previous lesson's last line assigned `NULL` to a pointer right after a free call and
left it as "the topic of the next lesson." This lesson measures why that assignment is a
mandatory habit: a pointer that keeps pointing at a freed block becomes a **dangling**
pointer, and this condition is one of two observations that fall into the core's `unowned`
class — no one is obligated to keep this pointer valid, and no one guarantees pulling it to
`NULL` either.

#### Null Pointer: A Checkable Condition

```c
// null_pointer.c — a null pointer becomes safe once checked before dereferencing
#include <stdio.h>
#include <stdlib.h>

static int *maybe_allocate(int succeed) {
    if (!succeed) {
        return NULL;
    }
    int *p = malloc(sizeof *p);
    if (p != NULL) {
        *p = 9;
    }
    return p;
}

int main(void) {
    int *failed = maybe_allocate(0);
    printf("%s %d\n", "is the failed call null", (int)(failed == NULL));

    int *succeeded = maybe_allocate(1);
    if (succeeded != NULL) {
        printf("%s %d\n", "value from the successful call", *succeeded);
    } else {
        printf("%s\n", "successful call unexpectedly null");
    }

    free(succeeded);
    return 0;
}
```

```
is the failed call null 1
value from the successful call 9
```

**PO17.** The call `maybe_allocate(0)` returns `NULL`, and the comparison
`failed == NULL` gives **1** — this shows a null pointer is an **expected and checkable**
condition. The call `maybe_allocate(1)` returns a real block; but this block, too, is
checked with `succeeded != NULL` before use, because `malloc` gives no guarantee of
succeeding under every condition. Checking a null pointer **before dereferencing** it is
this measurement's only rule: `*failed` was never written, because `failed` was already
known to be null.

#### Dangling Pointer: Continuing to Point at a Freed Block

After a pointer is freed with `free`, the value it carries **does not change** — it still
points at the same block, but that block is no longer valid. This condition is called a
**dangling pointer**. Dereferencing a dangling pointer (reading or writing through `*p`)
or handing it to `free` again (a double free) is undefined behavior: the result is most
often a silently wrong value, sometimes a crash, never reliable behavior. That is why this
lesson **never dereferences or re-frees any dangling pointer** — it only describes why this
is dangerous and runs its defined counterpart.

```c
// defined_counterpart.c — assigning NULL after freeing makes the dangling condition checkable
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *p = malloc(sizeof *p);
    if (p == NULL) {
        printf("%s\n", "allocation failed");
        return 1;
    }
    *p = 21;
    printf("%s %d\n", "value before freeing", *p);

    free(p);
    p = NULL;
    printf("%s %d\n", "is it null after freeing", (int)(p == NULL));

    if (p != NULL) {
        printf("%s %d\n", "branch never reached", *p);
    } else {
        printf("%s\n", "dereference skipped thanks to the check");
    }
    return 0;
}
```

```
value before freeing 21
is it null after freeing 1
dereference skipped thanks to the check
```

**PO18.** `p = NULL` is assigned **right after** the `free(p)` call; no other operation
comes between these two lines. The comparison `p == NULL` gives **1**, and the final `if`
block never reaches the `*p` line because `p != NULL` is **false** — the line "dereference
skipped thanks to the check" is the proof of this. Had the `p = NULL` assignment been
skipped, `p` would have become a **dangling pointer** still pointing at the freed block, and
the `p != NULL` check could not have caught it — because a dangling pointer's value is not
null, only invalid. This is the fundamental distinction between a null pointer and a
dangling pointer: one is a checkable **value** (null), the other is an uncheckable
**condition** (invalid but not appearing null).

A second danger of a dangling pointer is handing it to `free` again — this condition is
called a **double free**. After the `free(p)` call, if `p` still carries the old address, a
second `free(p)` call tries to free the same block twice; this too is undefined behavior
and usually corrupts the allocator's own internal state. The example above never writes
this second call, because calling `free(NULL)` after the `p = NULL` assignment is
harmless — the standard defines freeing a null pointer as **doing nothing**. This is a
second benefit of the `NULL` assignment: it makes not only dereferencing but also a
possible second free call harmless.

```c
// safe_second_call.c — free(NULL) is defined as harmless by the standard
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *p = malloc(sizeof *p);
    if (p == NULL) {
        printf("%s\n", "allocation failed");
        return 1;
    }
    *p = 4;
    printf("%s %d\n", "value before freeing", *p);

    free(p);
    p = NULL;

    free(p);
    printf("%s\n", "second free ran on null");
    return 0;
}
```

```
value before freeing 4
second free ran on null
```

**PO19.** This code calls `free(p)` **twice**, but a `p = NULL` line sits between the two
calls. The first call frees the real block; the second call operates on `NULL`, and because
the standard explicitly defines this as "doing nothing," the program reaches the line
"second free ran on null" without crashing. Had the `p = NULL` assignment on the line
between them been missing, this same code's second `free(p)` call would have tried to free
the same block **twice** — the double-free error defined above — and that is why it is not
attempted here.

Both members of the `unowned` class in this lesson trace back to the same root: the
`overwritten pointer` observation in the first lesson's core left a block unreachable by
overwriting the single pointer holding its address **without freeing it first** — a
**leak**. A dangling pointer is the opposite-direction error: the block has been freed, but
the pointer keeps pointing at it. Both mean "no one guaranteed this condition," which is
why both fall into the `unowned` class.

#### Multiple Copy Pointers: Why a NULL Assignment Is Not Enough

Both examples so far worked through a single pointer. In real code, multiple pointers can
point at the same block — one performs the free call, but the others stay unaware of the
`p = NULL` line.

```c
// copy_pointer.c — of two pointers pointing at the same block, only one is pulled to NULL
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *p = malloc(sizeof *p);
    if (p == NULL) {
        printf("%s\n", "allocation failed");
        return 1;
    }
    *p = 7;

    int *q = p;
    printf("%s %d\n", "do p and q show the same value", (int)(*p == *q));
    printf("%s %d\n", "do p and q carry the same address", (int)(p == q));

    free(p);
    p = NULL;
    printf("%s %d\n", "is p null after freeing", (int)(p == NULL));
    return 0;
}
```

```
do p and q show the same value 1
do p and q carry the same address 1
is p null after freeing 1
```

**PO20.** After the line `int *q = p;`, `p` and `q` carry **the same address** — both the
`*p == *q` and `p == q` comparisons give **1**. When `free(p)` runs and then `p = NULL`,
only the `p` variable **itself** is affected; `q` never appears in these two lines, so it
still carries the old address of the block that was freed with `free` — `q` is now a
**dangling pointer**. This code neither dereferences `q` after this point nor compares it
against `NULL`, because the value `q` carries is now invalid and trying to read it (even a
comparison) is undefined behavior. What this is meant to show is **not visible** while the
code runs — which is exactly the bound itself: `p = NULL` only changes the condition of the
variable named `p`, not the condition of `q`.

#### Dangling Pointers Across the Ownership Axis: Three Different Sources

Both examples up to this point produced the dangling condition from a single source:
freeing a `malloc` block with `free`. But a dangling pointer can arise a different way in
each of the course's four ownership classes (`language`, `caller`, `callee`, `unowned`);
none of this code is run here because each leads to undefined behavior, but all three tie
back to the same root this lesson measures.

In the `language` class, if a function returns the address of a local variable (something
like `return &local;`), the returned pointer becomes dangling the instant the function
returns — because it is the language itself that manages `local`'s lifetime, and that
lifetime is bound to the function's body. There is no `free` call here at all; the block
disappears not through a `malloc`, but through the call frame itself going away. The result
is still the same: the caller is left holding a pointer to an invalid address.

In the `caller` class, the dangling condition is the form seen in both this lesson's
examples: if the side that allocated the memory and carries the obligation to call `free`
forgets the `NULL` assignment after freeing (or carries a second copy, as in
`copy_pointer.c`), that pointer stays dangling.

In the `callee` class, the dangling condition comes from the opposite direction: if a
function frees a block it holds itself (think of the data a core like `owner.c` owns)
internally with `free`, but does not pull back a pointer it previously handed to the
caller for that block, the caller's own copy is now a dangling pointer — the caller has no
way of knowing this, because the callee never reported it in any way. This is why a
function in the `callee` class must have an explicit contract: documenting when it frees a
block, and that this freeing invalidates the caller's copies, is the callee side's
responsibility.

All three arrive at the same observation: a dangling pointer is a question about whether
the address a pointer **carries** still points at a valid block, and regardless of which
class it comes from, this question is never answered by the language itself — the answer
always falls to the programmer to track. The difference between the three sources is
**when** the question becomes noticeable: the error in the `language` class usually
surfaces the instant the function returns, on first use; the error in the `caller` class
can be delayed by a forgotten `NULL` assignment, as in this lesson; the error in the
`callee` class is the latest to be noticed, because nothing changes in the caller's own
code at all — only the internal behavior of the function it calls has changed.

#### The Difference from Rust's Borrow Checker

The question of who watches over freed memory in C sits in a different place from Rust's
borrow checker, covered in the Ownership and the Memory Model course (M10/K02): in Rust, a
value's lifetime is determined at compile time, and a reference whose lifetime has ended is
rejected by the compiler; in C, no mechanism checks whether a pointer still points at a
valid block, aside from a habit like `p = NULL`. In Rust, something resembling the
`copy_pointer.c` example above **would not compile at all** — the instant `p` is lent to
`q`, the borrow checker would prevent `p` from being freed while `q` is still alive. C has
no such check; knowing that `q` is dangling is left entirely to the kind of tracking a
programmer does, as done here in this lesson.

#### A Tool Catches the Dangling Pointer, Not the Compiler

Every check used in this lesson (`== NULL`, `!= NULL`) is hand-written, added by the
programmer themself — the compiler itself does not know whether a pointer is dangling and
never flags it automatically. In real projects, this gap is closed with runtime tools: a
dynamic analyzer tracks every `malloc`/`free` pair while a program runs, and reports it if a
block is accessed (dereferenced, double-freed) after being freed. These tools work not at
compile time but at **runtime**, and only for paths actually triggered in that particular
run — unlike Rust's compile-time borrow checker, a dangling-pointer error must actually run
at least once before it is caught. This sums up the nature of defense in C: the language
itself gives no guarantee, but discipline (the `NULL`-assignment habit seen in this lesson)
and tools (runtime analyzers) together make up for that missing guarantee.

#### The Bounding Measurement

The `p = NULL` assignment does **not eliminate** the dangling-pointer problem — it only
makes it visible for that one pointer. The `copy_pointer.c` example above showed this
concretely: `q`, despite carrying the same address as `p`, was never affected by the
`p = NULL` line at all. This lesson measured the single-pointer and two-pointer cases
separately; more complex sharing patterns — three or more pointers sharing the same block,
or a pointer stored in a struct field — are the topic of the course's Dynamic Memory topic,
in the Deallocation Discipline lesson. The core question stays the same there: who holds
the authority to free a block, and who informs the other copies that it has been freed.

#### Summary

- A null pointer (`NULL`) is a **checkable value**; it can be tested with `== NULL` before
  dereferencing, and this lesson applied that after every allocation call.
- A dangling pointer is a pointer that **keeps pointing** at a freed block; this lesson
  never dereferenced or re-freed one — it only described it.
- Assigning `NULL` to a pointer right after a `free` call **converts** the dangling
  condition into a checkable null condition.
- Null and dangling pointers are two different faces of the core's **unowned** class: one
  is an unfreed leak, the other is a freed but still-pointed-at block. `p = NULL` applies
  only to **that one pointer**; if another copy points at the same block, as
  `copy_pointer.c` showed, that copy is still dangling.
- The dangling condition can arise not only from a `caller` forgetting a `NULL`
  assignment, but also from a local variable's address being returned in the `language`
  class, or a `callee` freeing a block it holds without informing the caller — all three
  arrive at the same question (is the address still valid) by a different route.
- A double free (calling `free` on the same block twice) is also a consequence of a
  dangling pointer; the standard defining `free(NULL)` as harmless makes the `p = NULL`
  habit protective against this second danger as well — `safe_second_call.c` showed this
  concretely.

#### Next Step

The four lessons up to this point used pointers' **concrete types**, like `int *` and
`double *` — the type always said what value was being pointed at. The next lesson,
`void` and Opaque Pointers, measures conditions where a pointer's type is deliberately
**hidden**: the core's `opaque record` observation shows how it moves ownership to the
**callee** class while fully concealing the `Record` struct's contents. The dangling
pointer danger seen in this lesson still applies there — because the caller holding an
opaque record's address has the obligation to free it belonging to the **callee**, it can
only learn when the record becomes invalid from the callee's contract; because the record's
contents are hidden, that contract comes not from the type definition itself, but only from
documentation.

### `void` and Opaque Pointers

Source: https://academia.sh/en/courses/c-memory/void-and-opaque-pointers

How a void pointer deliberately hides the type it points to, and how an opaque type hides its contents entirely, keeping ownership inside the callee module.

The four lessons up to this point always knew explicitly what type a pointer's pointed-to
value was: `int *`, `double *`. This lesson measures two conditions where the type is
deliberately **hidden**: `void *` — a pointer that can point at any object type but cannot
itself be dereferenced — and the **opaque type** — a struct whose contents never leak
outside the unit that defines it. Both form the foundation of the core's `owner.h`/`owner.c`
unit; the class here is **callee**: the module holding the memory only lends it to the
caller.

#### `void *`: A Pointer That Carries a Type but Cannot Be Dereferenced

```c
// void_pointer.c — a void pointer holds any type but must first convert to a concrete type
#include <stdio.h>

static void print_int(const void *data) {
    const int *p = data;
    printf("%s %d\n", "value read as int", *p);
}

static void print_double(const void *data) {
    const double *p = data;
    printf("%s %.1f\n", "value read as double", *p);
}

int main(void) {
    int number = 17;
    double fractional = 3.5;

    void *general = &number;
    printf("%s %d\n", "does the general pointer equal the number's address", (int)(general == &number));

    print_int(general);
    general = &fractional;
    print_double(general);
    return 0;
}
```

```
does the general pointer equal the number's address 1
value read as int 17
value read as double 3.5
```

**PO21.** `general` is a `void *` variable and points first at `&number`, then at
`&fractional` — the same variable can carry the address of two objects of different types.
The comparison `general == &number` gives **1**; this shows a `void *` can be measured with
equality just like any other pointer. But `general` itself is never dereferenced as
`*general` anywhere — the `print_int` and `print_double` functions first cast it to their
own concrete types (`const int *`, `const double *`), and only then dereference it. This is
the rule of `void *`: it can **hold** any object pointer, but reading its pointed-to value
first requires **converting** it to a type.

#### `void *` Does Not Carry a Type: An External Tag Is Needed

The `general` variable in the previous section was always explicitly clear, **to a person
reading the code**, about which type it pointed to at each moment — first `&number`, then
`&fractional`. But at runtime, `void *` itself carries no type information at all; there is
no way to look at a `void *` variable and say whether it is an `int *` or a `double *`.
Programs usually close this gap with a **separate tag**.

```c
// tagged_value.c — a separate tag accompanies a void pointer since it carries no type
#include <stdio.h>

typedef enum { TYPE_INT, TYPE_DOUBLE } Type;

typedef struct {
    Type type;
    void *data;
} TaggedValue;

static void print(const TaggedValue *v) {
    if (v->type == TYPE_INT) {
        const int *p = v->data;
        printf("%s %d\n", "int-tagged value", *p);
    } else {
        const double *p = v->data;
        printf("%s %.1f\n", "double-tagged value", *p);
    }
}

int main(void) {
    int number = 42;
    double fractional = 2.5;

    TaggedValue values[2] = {
        { TYPE_INT, &number },
        { TYPE_DOUBLE, &fractional }
    };

    for (int i = 0; i < 2; i++) {
        print(&values[i]);
    }
    return 0;
}
```

```
int-tagged value 42
double-tagged value 2.5
```

**PO22.** The `TaggedValue` struct carries a `Type type` field alongside `void *data`; the
`print` function chooses the correct conversion **by looking at the `type` field**, not at
`data` itself — because `data` itself cannot say this. Each element of the `values` array
points at a different type but is held within the same `TaggedValue` type; this shows why
`void *` is usually used **not alone, but paired with a tag**. Had the tag been wrong (say,
an entry marked `TYPE_DOUBLE` but actually pointing at an `int *`), `print` would have
converted to the wrong type and read the wrong value — this kind of error is not caught by
the compiler, because `void *` compiles identically in either case.

This pattern also shows up in the C standard library: `qsort` and `bsearch` pass pairs of
`const void *` to their comparison functions, but never say anywhere what type they are
comparing — that information lives in the caller's head, inside the comparison function's
own body. `TaggedValue` moves this implicit contract into an **explicit field**: the type
information is no longer an assumption embedded in the function body, but a readable value
sitting next to the data. Both approaches carry the same risk — if the tag or the
assumption is wrong, the result is undefined — but `TaggedValue` at least makes that risk
**visible**, instead of assuming `sizeof` or parameter order will be correct on its own.

#### Opaque Type: A Struct Whose Contents Never Leak

The previous section hid `void *`'s type but not its size — the caller still knew which
concrete type to convert to. An opaque type goes one step further: it does not show the
caller even the struct's **fields**. This lesson measures this by rebuilding the core's
`owner.h`/`owner.c` unit inside this lesson's own files.

```c
// counter.h — a wrapper that counts every allocation and every free
#ifndef COUNTER_H
#define COUNTER_H

#include <stddef.h>

void *count_alloc(size_t size);
void count_free(void *block);
int allocated_count(void);
int freed_count(void);

#endif
```

```c
// counter.c — the counters are held only in this unit
#include <stdlib.h>
#include "counter.h"

static int allocated = 0;
static int freed = 0;

void *count_alloc(size_t size) {
    void *block = malloc(size);
    if (block != NULL) {
        allocated++;
    }
    return block;
}

void count_free(void *block) {
    if (block != NULL) {
        freed++;
    }
    free(block);
}

int allocated_count(void) {
    return allocated;
}

int freed_count(void) {
    return freed;
}
```

```c
// owner.h — the record's insides are hidden, the ownership contract is written in the comments
#ifndef OWNER_H
#define OWNER_H

#include <stddef.h>

typedef struct Record Record;

/* The caller takes ownership and frees it with record_destroy. Returns NULL
   if the name does not fit the limit; the function itself frees any memory
   allocated up to that point. */
Record *record_create(const char *name, size_t limit);

/* Frees the record and the name buffer inside it together. */
void record_destroy(Record *r);

/* The record retains ownership: the returned pointer is valid until the
   record is destroyed, the caller does not free it. */
const char *record_name(const Record *r);

/* The caller takes ownership: the returned copy is freed with count_free. */
char *record_name_copy(const Record *r);

#endif
```

```c
// owner.c — the unit where the contract is implemented
#include <string.h>
#include "owner.h"
#include "counter.h"

struct Record {
    char *name;
    size_t length;
};

Record *record_create(const char *name, size_t limit) {
    Record *r = count_alloc(sizeof *r);
    if (r == NULL) {
        return NULL;
    }
    r->length = strlen(name);
    if (r->length >= limit) {
        count_free(r);
        return NULL;
    }
    r->name = count_alloc(r->length + 1);
    if (r->name == NULL) {
        count_free(r);
        return NULL;
    }
    memcpy(r->name, name, r->length + 1);
    return r;
}

void record_destroy(Record *r) {
    if (r == NULL) {
        return;
    }
    count_free(r->name);
    count_free(r);
}

const char *record_name(const Record *r) {
    return r->name;
}

char *record_name_copy(const Record *r) {
    char *copy = count_alloc(r->length + 1);
    if (copy != NULL) {
        memcpy(copy, r->name, r->length + 1);
    }
    return copy;
}
```

```c
// opaque_record.c — the fields of the Record struct are defined only outside this unit
#include <stdio.h>
#include "owner.h"
#include "counter.h"

int main(void) {
    Record *r = record_create("inventory", 32);
    if (r == NULL) {
        printf("%s\n", "creation failed");
        return 1;
    }

    printf("%s %s\n", "record name", record_name(r));

    char *copy = record_name_copy(r);
    printf("%s %s\n", "copied name", copy != NULL ? copy : "none");
    count_free(copy);

    record_destroy(r);
    printf("%s %d\n", "counter diff after freeing", allocated_count() - freed_count());
    return 0;
}
```

```
record name inventory
copied name inventory
counter diff after freeing 0
```

**PO23.** The `opaque_record.c` file **never directly accesses any field** of
`struct Record` — `owner.h` only declares the `Record *` type and the four functions, the
struct's contents stay inside the `owner.c` unit. The call `record_name(r)` **lends** the
name inside the struct — the `lent name` row in `ownership.c` used this same call, and its
counter diff came out **0**, because a loan does not allocate memory. After the call
`record_destroy(r)`, `allocated_count() - freed_count()` gives **0** — both the record
itself and the name buffer inside it were freed in full. Ownership here belongs to the
**callee** class: the unit holding `Record` (`owner.c`) both creates and destroys it, the
caller only takes a **loan** through `record_name` and does not free that loan.

#### The Same Contract Across Multiple Instances

The contract in `owner.h` must not hold for a single `Record` instance only — it must hold
**identically for every instance**, both for successful creations and for the failed call
where `record_create` returns `NULL`.

```c
// multi_record.c — the same opaque contract stays valid across more than one Record instance
#include <stdio.h>
#include "owner.h"
#include "counter.h"

int main(void) {
    Record *first = record_create("warehouse", 32);
    Record *second = record_create("shipment", 32);
    Record *failed = record_create("anamefartoolongforthislimit", 8);

    printf("%s %d\n", "is the failed creation null", (int)(failed == NULL));

    if (first != NULL && second != NULL) {
        printf("%s %s\n", "first record name", record_name(first));
        printf("%s %s\n", "second record name", record_name(second));
    }

    record_destroy(first);
    record_destroy(second);
    record_destroy(failed);
    printf("%s %d\n", "counter diff after all frees",
           allocated_count() - freed_count());
    return 0;
}
```

```
is the failed creation null 1
first record name warehouse
second record name shipment
counter diff after all frees 0
```

**PO24.** `failed` comes back `NULL` because the given name
(`anamefartoolongforthislimit`) is longer than the given `limit` (`8`) —
`is the failed creation null` is **1**. `first` and `second` are independent `Record`
instances; each allocates its own `name` buffer, and the `record_name` calls do not affect
each other. The call `record_destroy(failed)` operates on `NULL` — `record_destroy` in
`owner.c` already handles this with its `r == NULL` check, so no separate check needs to be
written here. After all three calls (two successful, one failed),
`allocated_count() - freed_count()` gives **0** — the failed call had already freed its own
`r` block inside `record_create`, so the outside `record_destroy(NULL)` call doing nothing
does not throw off the counters. This is another face of the **callee** class: the contract
cleans up its own allocated memory not only on the success path, but also **on the failure
path**.

#### The Bounding Measurement

`void *` **retains** type information but does not hide it — the caller must know which
concrete type to convert to, otherwise the conversion is wrong and the result is undefined.
An opaque type goes beyond this: the caller **never knows at all** how many fields `Record`
has, or what the fields are named or sized — it interacts only through the functions
declared by `owner.h`. This is a flexibility-safety trade-off: an opaque type makes it
possible to change its internal structure (add a new field) without breaking the outer
interface, but every access requires a function call — there is no direct field access.

This guarantee only holds as long as the side using `void *` stays disciplined. In
`tagged_value.c`, `print` looks at the `v->type` field and picks the correct conversion; but
the compiler does not **check** this match — had we converted a `TYPE_DOUBLE` value to
`const int *` and dereferenced it without ever reading the `Type` field, the bit pattern in
memory, written as a `double`, would have been read as an `int`. This block is deliberately
never run: its result is undefined and varies by platform, compiler, even optimization
level. The code might never crash, or it might silently print a wrong number — this is the
real danger of undefined behavior, the error passing without giving any visible signal.
`TaggedValue`'s `type` field does not remove this risk, it only makes it **visible**: the
wrong conversion is still possible, but it now becomes an error preventable with a check
like `if (v->type == TYPE_INT)`. In a function taking a raw `void *` parameter, this check
is impossible, because the type information is not held anywhere; `tagged_value.c` fills
this gap by carrying the type alongside the pointer.

The same risk sits in a different place for the opaque type. Although `first` and `second`
in `multi_record.c` both carry the same `Record *` type, both of their `data`/`name` fields
are hidden inside `owner.c`'s file — the caller **cannot even attempt** to write
`first->name`, because the definition of the `Record` struct does not exist in `owner.h`,
only inside `owner.c`. This solves `void *`'s type-safety problem in a completely different
way: instead of removing the risk of a wrong conversion, it **closes off the path** to a
conversion **at compile time**. What the two approaches have in common is that both give the
same answer to the question of who owns the data: `record_create` and `record_destroy`
inside `owner.c` manage `first`'s and `second`'s lifetime from start to end themselves — the
caller only holds a handle (`Record *`) and never touches its contents. This is why the
lesson's ownership class is **callee**: `owner.c` both allocates and frees, the caller's
only job is to pass the handle it holds to the correct functions.

But this job is itself an obligation, and opacity does not hide it. An opaque type hides the
struct's **insides**, not its **ownership**: handing the handle returned by `record_create`
back to `record_destroy` is still the caller's job, and this obligation is written not in
the signature but only in the comment in `owner.h`. If the caller forgets this call, the
compiler says nothing; both blocks remain unowned.

#### Summary

- `void *` can **hold** any object pointer but cannot be dereferenced itself; it must first
  be converted to a concrete type (`int *`, `const double *`, and so on).
- A `void *` variable can carry the address of objects of different types over time;
  equality comparison works the same as any other pointer. But because it does not carry
  its type itself, which type it converts to is not checked by the compiler without a
  separate tag, as in the `TaggedValue` example — a wrong conversion is undefined behavior
  and is shown only by description, never run.
- An opaque type (`Record`) never leaks its fields **outside the unit that defines it**;
  the caller interacts only through the declared functions. `multi_record.c` shows this
  contract stays valid across multiple independent instances within the same program.
- The unit holding an opaque type both creates and destroys it — class **callee**; only
  loaned values (like `record_name`) are left to the caller. This also holds on the
  failure path: when `record_create` fails, it frees the block it allocated itself, and
  the caller's `record_destroy(NULL)` call is only a safe no-op.
- A loan (`record_name`, which returns `const char *`) and a copy (`record_name_copy`,
  which returns `char *`) carry different ownership — one must not be freed, the other
  must be.

#### Next Step

The Pointers topic's last lesson, Function Pointers, measures a kind separate from data
pointers: pointers that point at a function itself and can be passed as a parameter to
another function. This lesson measured the gap between `void *` carrying type information
without hiding it and an opaque type hiding it completely; a similar tension exists with
function pointers — a callback's signature says what context it will take, but guarantees
nothing about the context's contents or ownership. What context a callback carries and who
owns that context is this topic's closing question.

### Function Pointers

Source: https://academia.sh/en/courses/c-memory/function-pointers

Pointers that point at a function itself and can be passed as a parameter to another function; the ownership of a callback's context belongs to the caller.

The five lessons up to this point measured cases where a pointer points at **data** — an
integer, an array, an opaque record. This last lesson measures a pointer that points at
something different: **the function itself**. A function pointer holds the address of a
compiled function and lets that function be called indirectly — this is the foundation of
choosing a function at runtime, or of passing one to another function **as a parameter**.

A function pointer's declaration looks confusing at first glance: the parentheses in
`int (*op)(int, int)` are not arbitrary, they are required — had we written
`int *op(int, int)`, this would be the **declaration** of a function named `op` that
returns `int *`, not a pointer named `op`. The parentheses group `*op` as a name first and
tell the compiler "`op` is a pointer, and what it points at is a function matching this
signature." This complexity is usually hidden behind a `typedef` — indeed the
`void (*func)(int, void *)` parameter type in `callback_context.c` follows the same
pattern as the `BinaryOp` definition in `operation_table.c`; one just writes the type
directly, the other binds it to a name.

#### Defining and Calling a Function Pointer

```c
// function_pointer.c — a function pointer holds the address of a compiled function
#include <stdio.h>

static int add(int a, int b) {
    return a + b;
}

static int multiply(int a, int b) {
    return a * b;
}

int main(void) {
    int (*op)(int, int) = add;
    printf("%s %d\n", "indirect call through add", op(3, 4));

    op = multiply;
    printf("%s %d\n", "indirect call through multiply", op(3, 4));

    printf("%s %d\n", "is op equal to add", (int)(op == add));
    printf("%s %d\n", "is op equal to multiply", (int)(op == multiply));
    return 0;
}
```

```
indirect call through add 7
indirect call through multiply 12
is op equal to add 0
is op equal to multiply 1
```

**PO25.** The declaration `int (*op)(int, int)` says that `op` will hold the address of a
function that takes two `int`s and returns an `int`. After the assignment `op = add`, the
call `op(3, 4)` gives **7** — it indirectly runs `add`. `op` is then **reassigned** to
`multiply`, and the same call syntax (`op(3, 4)`) now gives **12**. `op == add` is **0**,
`op == multiply` is **1** — this shows function pointers can be compared with equality
just like data pointers; `op` matches whichever function it currently points at.

#### Callback: Passing a Function to Another Function

```c
// callback.c — passing a function as a parameter chooses behavior at runtime
#include <stdio.h>

static int square(int x) {
    return x * x;
}

static int negate(int x) {
    return -x;
}

static void apply_and_print(int value, int (*transform)(int), const char *label) {
    printf("%s %d\n", label, transform(value));
}

int main(void) {
    apply_and_print(6, square, "square transform");
    apply_and_print(6, negate, "negate transform");
    return 0;
}
```

```
square transform 36
negate transform -6
```

**PO26.** The `apply_and_print` function, through its `int (*transform)(int)` parameter,
lets **the caller decide which function runs** — the names `square` and `negate` never
appear inside the function's own body. The first call passes `square` and the result is
**36** (6*6), the second passes `negate` and the result comes out **-6**. This pattern
achieves different behaviors without changing the same `apply_and_print` body — the
behavior is chosen **as a parameter**, at call time rather than compile time. This pattern
is called a **callback**: `transform` calls back, from inside `apply_and_print`, into the
function the caller supplied.

What differs from the `add`/`multiply` example is that the function itself is no longer
**held** in a variable, it flows directly as a parameter. The `op` variable was persistent
inside `main` and could be reassigned; `transform`, on the other hand, is a temporary
parameter that only exists while `apply_and_print` is running. Both uses share the same
function-pointer mechanism, but one demonstrates **holding state** (which function `op`
currently points at), the other **one-shot dispatch** (which function to use for this
call).

#### Ownership of Context: The Data Carried to a Callback

A callback often carries not just a value, but a **context** that accompanies that value.
Who should own this context? The following example measures the case where the caller
supplies a context and manages that context's lifetime itself.

```c
// callback_context.c — the owner of the context carried to a callback is the caller that supplied it
#include <stdio.h>

typedef struct {
    int factor;
    int sum;
} Context;

static void accumulate(int value, void *context) {
    Context *ctx = context;
    ctx->sum += value * ctx->factor;
}

static void run_over_array(const int *array, int length,
                            void (*func)(int, void *), void *context) {
    for (int i = 0; i < length; i++) {
        func(array[i], context);
    }
}

int main(void) {
    int values[4] = {1, 2, 3, 4};
    Context ctx = {.factor = 3, .sum = 0};

    run_over_array(values, 4, accumulate, &ctx);
    printf("%s %d\n", "sum with factor", ctx.sum);

    ctx.factor = 1;
    ctx.sum = 0;
    run_over_array(values, 4, accumulate, &ctx);
    printf("%s %d\n", "sum without factor", ctx.sum);
    return 0;
}
```

```
sum with factor 30
sum without factor 10
```

**PO27.** The `Context` struct is defined as a **local variable** inside `main` — it is
never allocated with `count_alloc` or `malloc`, its class is **language**. But it is passed
as `&ctx` to `run_over_array`, and that function **forwards** it to `accumulate` through
the `void *context` parameter. `accumulate` does not take ownership of `context` itself —
it only writes into memory that `main` owns. On the first run the factor is **3** and the
sum is **30** (3*(1+2+3+4)); on the second run the factor is reset to **1** and the sum
comes out **10** — `ctx` can be reused because its lifetime still belongs to `main`. The
same rule would hold even if the context passed to a callback had been dynamically
allocated: the party **supplying** the context (the caller) would remain obligated to free
it, a callback function like `accumulate` never takes ownership of it itself.

This pattern shares the same ownership shape as this course's first lesson's opaque record
observation, but from the opposite direction: there, the **callee** (`owner.c`) held its
own data and only lent it to the caller; here, the **caller** holds its own data, and the
**callee** (`accumulate`, `run_over_array`) only accesses that data through a pointer. In
both cases the data stays with a single side; what changes is only which side holds the
memory. The `void *context` parameter here follows the same rule as the `void *` in the
void and opaque pointers lesson (05): the `accumulate` function first converts `context`
to the `Context *` type, and only then accesses its fields — the type information is
carried by the caller, the callback function only recovers it.

#### An Array of Function Pointers: A Dispatch Table

Just as a function pointer can be reassigned on its own, it can also be held inside an
**array** — this makes it possible to select which function runs at runtime through an
index.

```c
// operation_table.c — an array of function pointers builds a runtime dispatch table
#include <stdio.h>

typedef int (*BinaryOp)(int, int);

static int add(int a, int b) { return a + b; }
static int subtract(int a, int b) { return a - b; }
static int multiply(int a, int b) { return a * b; }

int main(void) {
    BinaryOp table[3] = { add, subtract, multiply };
    const char *names[3] = { "add", "subtract", "multiply" };
    int results[3];

    for (int i = 0; i < 3; i++) {
        results[i] = table[i](10, 4);
        printf("%s %d\n", names[i], results[i]);
    }

    int total = 0;
    for (int i = 0; i < 3; i++) {
        total += results[i];
    }
    printf("%s %d\n", "sum of results", total);
    return 0;
}
```

```
add 14
subtract 6
multiply 40
sum of results 60
```

This table is a dispatch form that replaces an `if`/`else if` chain or a `switch`
statement: adding a new operation is limited to adding an element to the `table` and
`names` arrays, the calling code itself does not change.

**PO28.** Each element of the `table` array holds the address of a different function; the
call `table[i](10, 4)` selects which function runs **at runtime, based on the index
`i`** — there is no need to write three separate lines like `add(10, 4)`, `subtract(10, 4)`,
`multiply(10, 4)` in the source code. The `results` array, like `table` itself, is defined
as a local variable inside `main`; both have class **caller** — `main` allocates them,
fills and reads them, and once the function calls are done they go out of scope and are
returned automatically. This is the same ownership shape as `Context ctx` in
`callback_context.c`: both the array and the struct are managed by the automatic lifetime
of the **language** class, but the array's *contents* (which functions sit in which order)
are entirely `main`'s decision — `add`, `subtract`, and `multiply` have no say in that
decision, they only do their own job when called.

#### The Bounding Measurement

A function pointer's signature (like `int (*)(int, int)`) only matches parameter and
return types — it says nothing about **what** the function it points at does or **what
its side effects** are. `add` and `multiply` above share the same signature but do
completely different things; a function taking a callback parameter gets no compile-time
guarantee at all about the behavior of the function passed to it. This is another form of
the same limit found in earlier lessons — that pointer types do not carry ownership
information: a type guarantees **shape**, not **meaning**.

This limit turns into a separate risk when combined with the context carried to a
callback. In `callback_context.c`, at the moment the `run_over_array` call passes to
`accumulate`, `ctx` is still within `main`'s scope, so the memory `context` points at is
valid. But if a callback is registered to run **later** — say, in an event queue, after the
calling function has already returned — the address passed as context may by then have
outlived the automatic lifetime of the **language** class — the same dangling pointer
condition from lesson 04, here in callback form. This block is deliberately never run: a
function would write the address of a local `Context` variable into a callback record and
return, then when the registered callback fires, `context` would be pointing at a call
frame whose scope has already closed — dereferencing it is undefined behavior. In the
`run_over_array` example this risk does not exist, because the callback runs while still
inside `main`'s own call frame, while `ctx` is still alive; the risk only appears when the
context's lifetime and the callback's execution moment **diverge**. This is why the
obligation of the party supplying the context (the caller) is not limited to freeing the
memory — it must also guarantee the context stays alive until whenever the callback might
fire.

This pattern's most common use is in the C standard library: `qsort` requires an
`int (*)(const void *, const void *)` comparison function to sort an array, `bsearch` uses
the same signature for searching. `qsort` itself does not **know** which type it is
sorting — the same indirection pattern as the `table[i]` call in `operation_table.c`, but
this time the function being indirected is a comparison decision supplied by the caller.
The comparison function passed to `qsort` also has to first convert its own parameters to
the correct type — just like `accumulate` converting its `void *context` to `Context *`
first; the `const void *` in the signature does not carry type information here either, it
only stores it. The difference between the dispatch-table pattern and the `qsort` pattern
is only scale: one chooses among three fixed functions, the other lets the caller supply
**a different comparison logic each time** — both rest on the same foundation: carrying a
function like data and calling it indirectly when needed.

#### The Ownership Axis Across This Topic: Four Classes, One Outcome

The Pointers topic's six lessons asked the same question in six different pointer forms:
**who owns this memory?** In lessons 01 and 02 the answer was **language** — local
variables and arrays inside `main` were returned automatically once scope closed, no side
called `free`. In lesson 03 ownership stayed with **language** again, but the problem was
less about ownership and more about **information loss**: an array lost its length when it
decayed to a pointer, which required `largest`, `matrix_decay.c`, and
`returns_length_too.c` to fill in the same missing information through three different
channels. In lesson 04 ownership split into three sources (language, caller, callee), and
the shared result was a dangling pointer — a value that keeps pointing at a block that has
already been freed. In lesson 05 ownership moved for the first time to a **hidden** class:
the opaque type was a contract where the **callee** held its own data and only lent it to
the caller. In this last lesson ownership returned to the **caller** again — but this time
what is at stake is not data, but a **function choice** and the context accompanying it;
the `Context ctx` in `callback_context.c` and the `table`/`results` arrays in
`operation_table.c` were memory that `main` allocated and `main` reclaimed.

What the four classes (language, caller, callee, unowned) have in common is that none of
them ever showed up in the pointer's **type** — neither `int *`, nor `Record *`, nor
`int (*)(int, int)` said which side was obligated to free it. This information always had
to be carried **from outside**, either through a naming convention (`_create`/`_destroy`
pairs), a comment, or a written contract. The next topic, Dynamic Memory, applies these
same four classes to the `malloc`/`free` pair itself; the classification here will not
change, only which functions represent which class will become clearer.

#### Summary

- A function pointer (`int (*)(int, int)`) holds the address of a compiled function and
  lets that function be called indirectly; it can be reassigned to different functions.
- Passing a function as a parameter to another function sets up a **callback**; the
  called function's behavior changes at runtime depending on which function is passed.
- The context carried to a callback is usually the address of a local variable of the
  **language** class; the callback function does not **own** this context, it only uses
  it.
- The party supplying the context is the **caller**, and even if the context had been
  dynamically allocated, the obligation to free it would still remain with the caller.
- A function pointer's signature only matches parameter and return types; it does not
  guarantee the behavior of the function it points at.
- An array of function pointers (`table` in `operation_table.c`) builds a dispatch table
  selected by index at runtime; `qsort`/`bsearch`'s comparison parameter uses the same
  pattern in the standard library.

#### Next Step

The Pointers topic closes here: the four classes (language, caller, callee, unowned) were
measured across six lessons, through both data pointers and function pointers. In every
lesson, what determined the class was never the pointer's type but which side took on
allocation and freeing — this distinction never showed up directly in the source code, it
was only ever conveyed through naming and contract. The next topic, Dynamic Memory, carries
these same four classes into the behavior of `malloc`, `calloc`, `realloc`, and `free`
themselves — failed allocation, the risks carried by resizing, and the discipline of
freeing.

## Dynamic Memory

Allocation, zero-filled allocation and resizing functions, documenting the deallocation contract in a header file, measuring a memory leak by a counter difference, whether a tool catches it or not, and memory being divided into automatic, static and dynamic regions.

### Memory Allocation Functions

Source: https://academia.sh/en/courses/c-memory/memory-allocation-functions

malloc, calloc, realloc, and free all work from the same heap and all four leave the same question: who frees the block you allocated? The answer is always the same — the allocator is the caller; this lesson measures that with the counter difference, reads the zeroing directly, and shows that resizing leaves the old ownership unchanged.

The previous six lessons always took for granted where the memory a pointer points to came
from: the address of a local variable, the first element of an array, a field inside a
record. Even the `Function Pointers` lesson carried a callback's context but never asked how
that context itself came to exist. This lesson closes that gap: a block, before anyone can
want it, must first be **allocated** — and whichever of the four functions the allocating
side calls, it takes on the same burden.

C's standard library provides four allocation and release functions: `malloc` returns an
unzeroed block of the requested size, `calloc` takes the element count and element size as
separate parameters and returns the block **zeroed**, `realloc` grows or shrinks an existing
block, `free` releases a block. All four carry the same contract: the returned pointer's
**owner is the caller** — whoever requests the block is also obligated to free it. On this
course's measurement axis, this is the most direct of the four classes; there is no need to
look for a contract here, the contract is in the function's own signature.

#### The Allocator Frees

The measurement is done with the same counting wrapper reused throughout the rest of the
course. `counter.c` counts every `count_alloc` and `count_grow` call, and every
`count_free` call; if the difference between the start and the end of an observation is
**0**, then everything allocated in that observation has been freed.

```c
// counter.h — a wrapper that counts every allocation and every free
#ifndef COUNTER_H
#define COUNTER_H

#include <stddef.h>

void *count_alloc(size_t size);
void *count_grow(void *old, size_t size);
void count_free(void *block);
int allocated_count(void);
int freed_count(void);

#endif
```

```c
// counter.c — the counters live only in this unit
#include <stdlib.h>
#include "counter.h"

static int allocated = 0;
static int freed = 0;

void *count_alloc(size_t size) {
    void *block = malloc(size);
    if (block != NULL) {
        allocated++;
    }
    return block;
}

void *count_grow(void *old, size_t size) {
    int new_block = (old == NULL);
    void *grown = realloc(old, size);
    if (grown != NULL && new_block) {
        allocated++;
    }
    return grown;
}

void count_free(void *block) {
    if (block != NULL) {
        freed++;
    }
    free(block);
}

int allocated_count(void) {
    return allocated;
}

int freed_count(void) {
    return freed;
}
```

These two files return under the same name and the same content in the remaining thirteen
lessons of the course; since this is where they first appear, they are written in full here.
`count_alloc` wraps `malloc`, `count_grow` wraps `realloc` — both only increment the counter
and call the real function, they do not change behavior.

```c
// alloc.c — measuring malloc, calloc, and realloc against the caller class
#include <stdio.h>
#include <stdlib.h>
#include "counter.h"

static void observe(const char *name, const char *class, const char *evidence) {
    printf("%-30s%-10s%s\n", name, class, evidence);
}

int main(void) {
    char buffer[64];
    int before, after;

    printf("%-30s%-10s%s\n", "observation", "owner", "evidence");

    /* --- malloc: raw block, the allocator frees --- */
    before = allocated_count() - freed_count();
    int *raw = count_alloc(3 * sizeof *raw);
    int total = 0;
    if (raw != NULL) {
        for (int i = 0; i < 3; i++) {
            raw[i] = i + 1;
            total += raw[i];
        }
    }
    count_free(raw);
    after = allocated_count() - freed_count();
    snprintf(buffer, sizeof buffer, "total %d, counter diff %d", total, after - before);
    observe("raw block", "caller", buffer);

    /* --- calloc: zeroed block, values read directly --- */
    int *zeroed = calloc(4, sizeof *zeroed);
    int all_zero = 1;
    if (zeroed != NULL) {
        for (int i = 0; i < 4; i++) {
            if (zeroed[i] != 0) {
                all_zero = 0;
            }
        }
    }
    snprintf(buffer, sizeof buffer, "all zero %d", all_zero);
    observe("zeroed block", "caller", buffer);
    free(zeroed);

    /* --- realloc: resizing does not create a new owner --- */
    before = allocated_count() - freed_count();
    int *growing = count_grow(NULL, 2 * sizeof *growing);
    int kept = 0;
    if (growing != NULL) {
        growing[0] = 7;
        int *grown = count_grow(growing, 8 * sizeof *grown);
        if (grown != NULL) {
            growing = grown;
        }
        kept = growing[0];
    }
    count_free(growing);
    after = allocated_count() - freed_count();
    snprintf(buffer, sizeof buffer, "first element %d, counter diff %d", kept, after - before);
    observe("resize", "caller", buffer);

    printf("allocated %d freed %d diff %d\n", allocated_count(), freed_count(),
           allocated_count() - freed_count());

    return 0;
}
```

```
observation                   owner     evidence
raw block                     caller    total 6, counter diff 0
zeroed block                  caller    all zero 1
resize                        caller    first element 7, counter diff 0
allocated 2 freed 2 diff 0
```

**DM1.** All three observations brought the counter difference back to **0**, and there is
one reason for this: right after each allocation call, or at the end of the function, there
is a matching free call. This is not a shared behavior of the four functions, it is **a
promise the caller makes**: `malloc`, `calloc`, and `realloc` do not impose a contract, they
only return a block; forgetting to free the block triggers neither a compiler warning nor a
linker error. `raw block` was used like a three-element array and yielded a total of **6** —
unlike the decayed arrays from the previous lesson, this was a block whose type was known
and whose length was fixed at the source; the difference was that this time the length came
not from a fixed definition but from a runtime request.

#### calloc's Zeroing

`malloc` allocates the requested size but does not touch its contents: the memory it returns
carries whatever was already there, and that value is **undefined** — reading it is not
safe, so the lesson never prints that value anywhere. `calloc` takes the same request with
two parameters — element count and element size — and **zeroes** the block it returns. The
`zeroed block` observation proves this directly by reading it: all four `int`s were found to
be **zero**, without any assignment.

This difference is not just a convenience. A block allocated with `malloc`, if not zeroed by
hand before first use, may carry the leftover of a previous allocation in every value read
from it — `calloc` moves this risk into the function's own contract. It matters for the same
reason that `calloc` checks the multiplication of the two elements for overflow **itself**
(if `element_count * element_size` exceeds an integer limit): writing
`malloc(element_count * element_size)` leaves that check to the caller, writing
`calloc(element_count, element_size)` brings the check inside the function. Ownership does
not change here either — the zeroed block also belongs to the **caller** and must be
released with `free`, which the lesson does on the last line.

#### Resizing Does Not Create a New Owner

`realloc` (here through the `count_grow` wrapper) is the trickiest of the four functions,
because a single call can do three different things: the block grows in place, the block is
moved to another address and the old address is freed, or the request cannot be satisfied
and `NULL` is returned. The `resize` observation shows the first two cases in a single flow:
`growing` was allocated with two elements, its first element was written as **7**, then it
was grown to eight elements. The `first element 7` value in the output proves that the
**content was carried over**, regardless of whether the address changed during the
growth — even if `realloc` moved to a new location, it copied the old content into the new
block itself.

What matters for ownership is this: before the growth, `growing`'s owner was the caller,
after the growth it is still the caller — **no new owner was born**, only the address in the
same owner's hand may have changed. The counter confirms this: the `count_grow(NULL, ...)`
call incremented the allocation counter because it was counted as a new block, while the
second `count_grow(growing, ...)` call **did not count a new allocation** — the `new_block`
check in `counter.c` exists exactly for this. At the end of the observation, the
`count_free(growing)` call frees a single block and the difference returns to **0**.

The structure of the code draws attention to the `grown` variable: the return of
`count_grow` is not written directly over `growing`, it is first taken into a separate
variable and checked for `NULL`. The reason is that on a failed resize, **the old block is
still valid and still belongs to the caller**: if `realloc` cannot satisfy the request, it
does not free the original block, it leaves it as it is and returns `NULL`. Writing the
return directly as `growing = count_grow(growing, ...);` would turn `growing` itself into
`NULL` on failure — from that point on no pointer would remain that could reach the old
block, and the block would become **unowned**, appearing as a leak. This lesson's code does
not exercise this mistake, because the request here is always small enough to succeed; but
the flow — taking it into a separate variable, checking for `NULL`, overwriting only on
success — is built in a way that also blocks exactly this second pattern. **The difference
between the right pattern and the wrong pattern is a single line of assignment**, but the
result is the difference between an owner still being able to find its block and losing it
entirely.

#### Chained Growth

The previous observation grew `growing` in a single step. But growing an array repeatedly
as it fills is also a common pattern — instead of growing the block by one element each time
an element is added, doubling it whenever it fills up. What is interesting about this
pattern from an ownership standpoint is that no matter how many times it is grown, the
block is still counted as **a single allocation**.

```c
// chain.c — repeated growth only increments the counter once
#include <stdio.h>
#include "counter.h"

int main(void) {
    char buffer[128];
    int before = allocated_count() - freed_count();

    int capacity = 1;
    int *array = count_grow(NULL, capacity * sizeof *array);
    int filled = 0;

    for (int i = 0; i < 5; i++) {
        if (filled == capacity) {
            capacity *= 2;
            int *grown = count_grow(array, capacity * sizeof *array);
            if (grown == NULL) {
                break;
            }
            array = grown;
        }
        array[filled] = i + 1;
        filled++;
    }

    int total = 0;
    for (int i = 0; i < filled; i++) {
        total += array[i];
    }

    int mid_diff = (allocated_count() - freed_count()) - before;
    count_free(array);
    int end_diff = (allocated_count() - freed_count()) - before;

    snprintf(buffer, sizeof buffer,
             "filled %d, capacity %d, total %d, diff after growth %d, diff after free %d",
             filled, capacity, total, mid_diff, end_diff);
    printf("%-30s%s\n", "chained growth", buffer);

    return 0;
}
```

```
chained growth                filled 5, capacity 8, total 15, diff after growth 1, diff after free 0
```

**DM2.** The loop grew `capacity` three times, going 1 → 2 → 4 → 8, so `count_grow` was
called four times in total (including the first allocation). Yet once the growths are done,
the counter difference is still **1**: the `new_block` check in `counter.c` only increments
`allocated` on the first call, where `old == NULL`; the following three calls are counted as
a **continuation** of the same block. This proves that the rule shown in a single step in
"Resizing Does Not Create a New Owner" does not change no matter how many steps it is spread
across: ownership does not look at how many times a hand changed along the chain, it looks
at how many times it started with a **new** allocation. After the `count_free(array)` call,
the difference returns to **0** — at the end of the chain, just as at its start, there was a
single owner.

#### A Shrinking Block

Chained growth always expanded the block; but `realloc` can also **shrink** a block under
the same contract. Shrinking is not the mirror image of growing: the implementation may
shrink the block in place, or it may move it to a smaller address, but in either case the
answer to the ownership question does not change — a shrunk block still belongs to the
caller.

```c
// shrink.c — shrinking with realloc keeps the same caller ownership
#include <stdio.h>
#include "counter.h"

int main(void) {
    char buffer[96];
    int before = allocated_count() - freed_count();

    int *wide = count_grow(NULL, 10 * sizeof *wide);
    for (int i = 0; i < 10; i++) {
        wide[i] = i;
    }
    int first_diff = (allocated_count() - freed_count()) - before;

    int *narrow = count_grow(wide, 2 * sizeof *narrow);
    int kept_first = -1, kept_second = -1;
    if (narrow != NULL) {
        kept_first = narrow[0];
        kept_second = narrow[1];
    }
    int shrink_diff = (allocated_count() - freed_count()) - before;

    count_free(narrow);
    int end_diff = (allocated_count() - freed_count()) - before;

    snprintf(buffer, sizeof buffer,
             "first element %d, second element %d, growth diff %d, shrink diff %d, diff after free %d",
             kept_first, kept_second, first_diff, shrink_diff, end_diff);
    printf("%-30s%s\n", "shrinking block", buffer);
    return 0;
}
```

```
shrinking block               first element 0, second element 1, growth diff 1, shrink diff 1, diff after free 0
```

**DM3.** `narrow` was allocated small enough to hold only two of `wide`'s first ten
elements; yet the `kept_first` and `kept_second` values carry over exactly the **0** and
**1** written before the shrink — `realloc` does not corrupt the remaining content when it
shrinks a block, it only frees the now-excess space. The counter difference did not change
at all from `first_diff` to `shrink_diff`: the allocation stayed single, both the growth and
the shrink were a continuation of the same block. This shows that the rule from "Resizing
Does Not Create a New Owner" is direction-independent — the rule is not specific to growing,
it rests on `realloc`'s ability to change the size **in either direction**.

#### The Bounding Measurement

A `malloc` or `calloc` call with a size of **zero** is left open by the standard in both
directions: an implementation may either return `NULL` or return a valid pointer of zero
size that can be freed with `free`. This is an environment-dependent behavior, and which one
is chosen is **not printed** here — the only thing read at the source is that the caller
must write with the knowledge that either of these two possibilities can happen. The
**ownership** guarantee the four allocation functions give does not change — if the returned
pointer is not `NULL`, its owner is still the caller — but this guarantee says nothing about
what the return will be for a zero-size request.

The same uncertainty applies to a `realloc(block, 0)` call: on some implementations this
call has the same effect as `free(block)` and returns `NULL`, on others it returns a valid
block of zero size. The difference between the two affects whether the caller **still holds
the block** — in the first case the block has already been freed, in the second it still
belongs to the caller and a separate `free` call is required. Because of this uncertainty,
this course's code never calls `realloc` with a zero size anywhere; when a size needs to be
brought down to zero, the block is freed directly with `count_free` and the pointer is
separately assigned `NULL`. Where the ownership question can have two different
environment-dependent answers, the lesson chooses not to ask that question at all.

#### Summary

- The owner of the block returned by `malloc`, `calloc`, and `realloc` is always the
  **caller**; the memory class this lesson measures is the **caller**.
- `malloc` does not zero the content, `calloc` does — this difference was shown in the
  measurement by reading the values directly, not by assuming it.
- `realloc` does not create a new owner; content is preserved, the counter only increments
  on the first allocation.
- A failed `realloc` does not free the old block; writing the return directly over the old
  variable leaves the old block unowned, which is why the return is first checked in a
  separate variable.
- A zero-size request with `malloc(0)` and `calloc` is implementation-dependent and is not
  printed in this lesson's measurement.

#### Next Step

This lesson measured **how** ownership begins: as soon as a block is allocated, the
obligation to free it passes to the caller. But **where that obligation is written down**
has not yet been asked — `count_alloc` and `count_free` themselves say "you will free this"
nowhere; this information is only inferred from the function's name and from habit. The
next lesson puts this obligation in writing, in a header file, and shows the first mechanism
by which ownership passes from the caller to the **callee**: of two functions that both
return the same text, one's owner is the side that frees it, the other's owner is the
caller.

### Deallocation Discipline

Source: https://academia.sh/en/courses/c-memory/deallocation-discipline

When a function returns a piece of text, who owns that text is not written in the signature — what writes it is the comment in the header file. Two functions pointing at the same record, one lending it and one returning a copy, show how the same contract can bind to two different owners.

The previous lesson showed that all four allocation functions carry the same contract:
the allocator frees. But this is not the contract's **only form**. When a function returns
a pointer, the signature does not decide who that pointer's owner will be — two functions
that return `char *`, with no visible difference between them, can carry two entirely
different obligations. This lesson measures that difference with two functions that point
at the same record: one keeps the memory **inside the module** and only lends it, the other
returns the caller a **new block**.

#### The Contract Is Written in the Header File

The measurement is done with the `owner.h`/`owner.c` pair reused throughout the rest of the
course; both are built on top of the counting wrapper (`counter.h`/`counter.c`), so they
appear in the same unit. The inside of the record (the fields of `struct Record`) is not
visible in `owner.h` — there is only a name (`Record`) and four function signatures. The
ownership information is **in the comment above** the signature.

```c
// counter.h — a wrapper that counts every allocation and every free
#ifndef COUNTER_H
#define COUNTER_H

#include <stddef.h>

void *count_alloc(size_t size);
void *count_grow(void *old, size_t size);
void count_free(void *block);
int allocated_count(void);
int freed_count(void);

#endif
```

```c
// counter.c — the counters live only in this unit
#include <stdlib.h>
#include "counter.h"

static int allocated = 0;
static int freed = 0;

void *count_alloc(size_t size) {
    void *block = malloc(size);
    if (block != NULL) {
        allocated++;
    }
    return block;
}

void *count_grow(void *old, size_t size) {
    int new_block = (old == NULL);
    void *grown = realloc(old, size);
    if (grown != NULL && new_block) {
        allocated++;
    }
    return grown;
}

void count_free(void *block) {
    if (block != NULL) {
        freed++;
    }
    free(block);
}

int allocated_count(void) {
    return allocated;
}

int freed_count(void) {
    return freed;
}
```

```c
// owner.h — the record's inside is hidden, the ownership contract is in the comments
#ifndef OWNER_H
#define OWNER_H

#include <stddef.h>

typedef struct Record Record;

/* The caller owns it and frees it with record_destroy. If the name does not
   fit the limit, returns NULL; the function frees what it allocated so far. */
Record *record_create(const char *name, size_t limit);

/* Frees the record together with the name buffer inside it. */
void record_destroy(Record *r);

/* The record owns it: the returned pointer is valid until the record is
   destroyed, and the caller does not free it. */
const char *record_name(const Record *r);

/* The caller owns it: the returned copy is freed with count_free. */
char *record_name_copy(const Record *r);

#endif
```

```c
// owner.c — the unit where the contract is carried out
#include <string.h>
#include "owner.h"
#include "counter.h"

struct Record {
    char *name;
    size_t length;
};

Record *record_create(const char *name, size_t limit) {
    Record *r = count_alloc(sizeof *r);
    if (r == NULL) {
        return NULL;
    }
    r->length = strlen(name);
    if (r->length >= limit) {
        count_free(r);
        return NULL;
    }
    r->name = count_alloc(r->length + 1);
    if (r->name == NULL) {
        count_free(r);
        return NULL;
    }
    memcpy(r->name, name, r->length + 1);
    return r;
}

void record_destroy(Record *r) {
    if (r == NULL) {
        return;
    }
    count_free(r->name);
    count_free(r);
}

const char *record_name(const Record *r) {
    return r->name;
}

char *record_name_copy(const Record *r) {
    char *copy = count_alloc(r->length + 1);
    if (copy != NULL) {
        memcpy(copy, r->name, r->length + 1);
    }
    return copy;
}
```

**DM4.** The signatures of `record_name` and `record_name_copy` are nearly identical — both
take a `const Record *`, both return a pointer to a `char` array. The difference between
them sits nowhere the compiler can check; it sits only in the comment. This is a short
summary of how C carries memory ownership: **the type system does not enforce an
obligation, it only enforces a sentence one person wrote and another person reads.**

#### Lent Versus Owned

`record_name` returns a pointer directly into the `name` field inside the record — it does
not allocate new memory. This pointer is **lent**: its validity depends on the record
itself, and lasts until the record is destroyed with `record_destroy`. `record_name_copy`,
on the other hand, allocates a **new** block with `count_alloc` and copies the same text
into it; this block is independent of the record's lifetime, and its owner is now the
caller.

```c
// contract.c — contrasting a lent name with a returned copy
#include <stdio.h>
#include <string.h>
#include "owner.h"
#include "counter.h"

static void observe(const char *name, const char *class, const char *evidence) {
    printf("%-30s%-10s%s\n", name, class, evidence);
}

int main(void) {
    char buffer[64];
    int before, after;

    printf("%-30s%-10s%s\n", "observation", "owner", "evidence");

    Record *r = record_create("contract", 16);

    /* --- callee: the module owns it, the caller does not free it --- */
    before = allocated_count() - freed_count();
    const char *lent = record_name(r);
    after = allocated_count() - freed_count();
    snprintf(buffer, sizeof buffer, "name %s, counter diff %d", lent, after - before);
    observe("lent name", "callee", buffer);

    /* --- caller: the owner of the returned copy is the caller --- */
    before = allocated_count() - freed_count();
    char *copy = record_name_copy(r);
    int equal = (copy != NULL) && (strcmp(copy, lent) == 0);
    count_free(copy);
    after = allocated_count() - freed_count();
    snprintf(buffer, sizeof buffer, "equal text %d, counter diff %d", equal, after - before);
    observe("returned copy", "caller", buffer);

    /* --- lent pointer still valid: copy freed, record still standing --- */
    const char *lent_after = record_name(r);
    snprintf(buffer, sizeof buffer, "name %s, same as before %d", lent_after, lent_after == lent);
    observe("lent pointer still valid", "callee", buffer);

    /* --- once the record is destroyed, the lent pointer follows its lifetime --- */
    before = allocated_count() - freed_count();
    record_destroy(r);
    after = allocated_count() - freed_count();
    snprintf(buffer, sizeof buffer, "counter diff %d", after - before);
    observe("record destruction", "callee", buffer);

    return 0;
}
```

```
observation                   owner     evidence
lent name                     callee    name contract, counter diff 0
returned copy                 caller    equal text 1, counter diff 0
lent pointer still valid      callee    name contract, same as before 1
record destruction            callee    counter diff -2
```

**DM5.** In the `lent name` observation the counter difference is **0** — `record_name`
allocated nothing, freed nothing; it only copied a pointer that already pointed at an
existing block. The `returned copy` observation is also **0**, but for a different reason:
here an allocation **did** happen, and it was freed right away with `count_free(copy)`. Two
zeros hide two different stories — one is zero because it did no allocation at all, the
other is zero because it freed what it allocated. The **-2** in the `record destruction`
observation is expected: `record_destroy` freed two blocks (the name and the record
itself), reallocating neither; the counter difference turned negative because this time only
the freeing side was measured.

The `lent pointer still valid` observation shows that the lent pointer **depends on the
record's lifetime**: the `record_name_copy` call allocated and freed a separate block, but
never touched the record itself — which is why the second call to `record_name` returned
the **same** address (`lent_after == lent`). The only thing that invalidates the lent
pointer is `record_destroy` itself.

#### Two Independent Copies

`record_name_copy` allocates a **new** block every time it is called; this means that if two
copies are requested from the same record, two separate owners are born. If one of the
copies is freed early, the other is entirely unaffected by it — because the only thing the
two share is nothing beyond an address.

```c
// two_copies.c — two copies taken from the same record have independent lifetimes
#include <stdio.h>
#include <string.h>
#include "owner.h"
#include "counter.h"

int main(void) {
    char buffer[96];
    Record *r = record_create("independent", 16);

    int before = allocated_count() - freed_count();
    char *copy_a = record_name_copy(r);
    char *copy_b = record_name_copy(r);
    int mid_diff = (allocated_count() - freed_count()) - before;

    count_free(copy_a);
    int b_still_standing = (strcmp(copy_b, "independent") == 0);
    int inner_diff = (allocated_count() - freed_count()) - before;

    count_free(copy_b);
    int end_diff = (allocated_count() - freed_count()) - before;
    record_destroy(r);

    snprintf(buffer, sizeof buffer,
             "diff after two allocations %d, when a is freed %d, b still readable %d, when both are freed %d",
             mid_diff, inner_diff, b_still_standing, end_diff);
    printf("%-30s%s\n", "two independent copies", buffer);

    return 0;
}
```

```
two independent copies        diff after two allocations 2, when a is freed 1, b still readable 1, when both are freed 0
```

**DM6.** `copy_a` and `copy_b` were derived from the same source (`r->name`), but both were
born through `record_name_copy` with a **separate** `count_alloc` call — which is why the
counter difference after two allocations is **2**. The `count_free(copy_a)` call only
affects `copy_a`'s block; `copy_b` is still valid and `strcmp` confirms this
(`b still readable 1`). This is the exact opposite pattern of `record_name` returning a
single lent pointer: there, two calls on the same record gave the **same** address
(`lent_after == lent`); here, two `record_name_copy` calls on the same record give two
**different** blocks. The ownership class is the caller in both cases, but each copy carries
its own independent ownership — freeing one has no effect on the other's contract
whatsoever.

#### Cleanup at the Boundary

The comment in `record_create`'s header file also records a third possibility: if the name
does not fit within the given limit, the function returns `NULL`, but it **itself** frees
whatever memory it had allocated up to that point. This shows that the callee class carries
an obligation not only on a successful return but also on a **failed** one — the module
cleans up its own unfinished work, leaving nothing behind for the caller.

```c
// limit.c — when record_create does not fit the limit, it frees what it allocated itself
#include <stdio.h>
#include "owner.h"
#include "counter.h"

int main(void) {
    char buffer[64];
    int before = allocated_count() - freed_count();

    Record *failed = record_create("a-very-long-name", 4);
    int is_null = (failed == NULL);
    int diff = (allocated_count() - freed_count()) - before;

    snprintf(buffer, sizeof buffer, "is NULL %d, counter diff %d", is_null, diff);
    printf("%-30s%s\n", "name over the limit", buffer);

    return 0;
}
```

```
name over the limit           is NULL 1, counter diff 0
```

**DM7.** `"a-very-long-name"` is sixteen characters long, the limit is **4** —
`record_create` allocates a block for `r`, then finds the length check failed, and calls
`count_free(r)` without ever allocating `r->name`, returning `NULL`. The counter difference
is still **0**: a single allocation, a single free, both happened inside the function
itself. The caller sees that `failed` is `NULL` but never knows which block was allocated
and which was freed — and does not need to know, because the contract stays on the module
even in a failure case.

#### The Bounding Measurement

The comment in `owner.h` is binding in both directions. If the caller frees the pointer
returned by `record_name` with `count_free`, the record's own free call (the
`count_free(r->name)` inside `record_destroy`) frees the same block a **second time** —
this is undefined behavior, and it is not run in this lesson. The reverse direction can
break the same way: if the caller never frees the block returned by `record_name_copy`, then
because the block is independent of the record's lifetime, destroying the record does not
rescue it either — it leaks. Both directions of the contract are written in the same comment, but the
compiler checks neither; the only thing that checks is a person reading the code.

What these two ways of breaking have in common is that **neither is visible from the
signature**. `char *record_name(const Record *r)` and `char *record_name_copy(const Record
*r)` are equivalent in the compiler's eyes — both return a pointer to a `char` array, both
take a `const Record *`. If the caller handles the wrong function the wrong way (tries to
free `record_name`'s return, or never frees `record_name_copy`'s return), compilation still
succeeds; the error only surfaces at runtime, as a double free or a leak. This is the same
class of error as **overwriting** a failed `realloc`'s return in the previous lesson: both
are syntactically valid, both are ownership violations the compiler cannot catch. The
difference is the source — there the error came from a return value being ignored, here it
comes from a comment not being read.

`owner.c` itself has no defense against these two ways of breaking; the one who takes on
that defense is the module's **user**. The `record_create`/`record_destroy` pair keeps a
record's entire lifetime under the module's own control — the caller is safe as long as it
uses this pair symmetrically. Storing the pointer `record_name` lent in a variable and
reading that variable after destroying the record is also an error from the same family
(a dangling pointer); this lesson does not run that separately, because it already returns
to M10/K02's lifetime question — the subject there was **when it ends**, and here it is
**who is obligated to free it**, and both break here at the same time.

**This lesson does not overlap with the lifetime question measured in M10/K02.** There, what
was measured was **where** a value's lifetime ends, and **the language** decided that place.
What is measured here is **who is obligated** to end that lifetime, and what decides that is
not the language but the **contract** in `owner.h`. `record_name` and `record_name_copy`
give the same text, and both of their lifetimes end eventually — but one leaves that ending
to the record's owner, the other hands it to the caller.

#### Summary

- Who owns a function's returned pointer is understood not from the signature but from the
  **comment** in the header file; the memory class this lesson measures is the **callee**.
- `record_name` lends: the counter difference is **0**, its validity depends on the
  record's lifetime.
- `record_name_copy` allocates a new block: the counter difference is also **0**, but this
  time an allocation and a free actually happened — its owner is now the caller.
- Breaking the contract (freeing something lent, or never freeing a copy) produces a double
  free or a leak; neither was run in this lesson, only described.
- The obligation question in this lesson is separate from the lifetime question in M10/K02:
  one asks **who will free it**, the other asks **when it ends**.

#### Next Step

What happens if the copy `record_name_copy` returns is never freed was only described in
this lesson — not run. The next lesson measures exactly that: a copy whose return is never
held leaves behind a block no pointer can reach any longer, and this block does not appear
as an error message or a crash — only as a counter difference.

### Memory Leaks

Source: https://academia.sh/en/courses/c-memory/memory-leaks

A memory leak gives no error message and crashes no program — it only widens the gap between what was allocated and what was freed. This lesson produces a leak two different ways, catches both with the counter difference, and shows why the total comes out to exactly 2.

The previous lesson only **described** how a contract can be broken — neither a double free
nor an unfreed copy was actually run there. This lesson actually runs one form of a broken
contract: a block that no one is obligated to free. This belongs to none of the previous
three classes (language, caller, callee) — the course's fourth and final class is
**unowned**. The unowned class is not a design, it is a defect; and the only place it
appears is the **counter difference**.

#### The Same Result, Two Ways

A block can end up unowned in two different ways: by overwriting the only pointer that
reaches it, or by never holding on to the return value of a call that returns it. Both
produce the same thing — a block no variable can reach any longer — but one is an assignment
mistake, the other is an oversight.

```c
// counter.h — a wrapper that counts every allocation and every free
#ifndef COUNTER_H
#define COUNTER_H

#include <stddef.h>

void *count_alloc(size_t size);
void *count_grow(void *old, size_t size);
void count_free(void *block);
int allocated_count(void);
int freed_count(void);

#endif
```

```c
// counter.c — the counters live only in this unit
#include <stdlib.h>
#include "counter.h"

static int allocated = 0;
static int freed = 0;

void *count_alloc(size_t size) {
    void *block = malloc(size);
    if (block != NULL) {
        allocated++;
    }
    return block;
}

void *count_grow(void *old, size_t size) {
    int new_block = (old == NULL);
    void *grown = realloc(old, size);
    if (grown != NULL && new_block) {
        allocated++;
    }
    return grown;
}

void count_free(void *block) {
    if (block != NULL) {
        freed++;
    }
    free(block);
}

int allocated_count(void) {
    return allocated;
}

int freed_count(void) {
    return freed;
}
```

```c
// owner.h — the record's inside is hidden, the ownership contract is in the comments
#ifndef OWNER_H
#define OWNER_H

#include <stddef.h>

typedef struct Record Record;

/* The caller owns it and frees it with record_destroy. If the name does not
   fit the limit, returns NULL; the function frees what it allocated so far. */
Record *record_create(const char *name, size_t limit);

/* Frees the record together with the name buffer inside it. */
void record_destroy(Record *r);

/* The record owns it: the returned pointer is valid until the record is
   destroyed, and the caller does not free it. */
const char *record_name(const Record *r);

/* The caller owns it: the returned copy is freed with count_free. */
char *record_name_copy(const Record *r);

#endif
```

```c
// owner.c — the unit where the contract is carried out
#include <string.h>
#include "owner.h"
#include "counter.h"

struct Record {
    char *name;
    size_t length;
};

Record *record_create(const char *name, size_t limit) {
    Record *r = count_alloc(sizeof *r);
    if (r == NULL) {
        return NULL;
    }
    r->length = strlen(name);
    if (r->length >= limit) {
        count_free(r);
        return NULL;
    }
    r->name = count_alloc(r->length + 1);
    if (r->name == NULL) {
        count_free(r);
        return NULL;
    }
    memcpy(r->name, name, r->length + 1);
    return r;
}

void record_destroy(Record *r) {
    if (r == NULL) {
        return;
    }
    count_free(r->name);
    count_free(r);
}

const char *record_name(const Record *r) {
    return r->name;
}

char *record_name_copy(const Record *r) {
    char *copy = count_alloc(r->length + 1);
    if (copy != NULL) {
        memcpy(copy, r->name, r->length + 1);
    }
    return copy;
}
```

```c
// leak.c — comparing an overwritten pointer with an ignored return
#include <stdio.h>
#include "owner.h"
#include "counter.h"

static int diff(void) {
    return allocated_count() - freed_count();
}

static void observe(const char *name, const char *class, const char *evidence) {
    printf("%-30s%-10s%s\n", name, class, evidence);
}

int main(void) {
    char buffer[64];
    int before;
    int first_diff, second_diff;

    printf("%-30s%-10s%s\n", "observation", "owner", "evidence");

    /* --- overwritten pointer: the first block is unreachable --- */
    before = diff();
    int *lost = count_alloc(sizeof *lost);
    lost = count_alloc(sizeof *lost);
    count_free(lost);
    first_diff = diff() - before;
    snprintf(buffer, sizeof buffer, "counter diff %d", first_diff);
    observe("overwritten pointer", "unowned", buffer);

    /* --- ignored return: the copy disappears without ever being held --- */
    Record *r = record_create("draft", 16);
    before = diff();
    if (r != NULL) {
        record_name_copy(r);
    }
    second_diff = diff() - before;
    snprintf(buffer, sizeof buffer, "counter diff %d", second_diff);
    record_destroy(r);
    observe("ignored return", "unowned", buffer);

    printf("total unowned counter diff %d\n", first_diff + second_diff);

    return 0;
}
```

```
observation                   owner     evidence
overwritten pointer           unowned   counter diff 1
ignored return                unowned   counter diff 1
total unowned counter diff 2
```

**DM8.** In the `overwritten pointer` observation, `lost` is allocated twice but freed only
once: when the second `count_alloc` call overwrites `lost`, no variable remains holding the
address the first call returned. `count_free(lost)` can only free the **second** block — the
first can no longer be reached by any path. The counter difference is **1**: one block was
allocated, and its counterpart was never freed.

**DM9.** The `ignored return` observation reaches the same result by a different path.
`record_name_copy` allocates a new block every time it is called and **returns** it — the
function itself does not free the block, because its contract leaves that to the caller (the
"Deallocation Discipline" lesson). Here the return value was never assigned to any variable;
the function ran successfully, the block was allocated, but no pointer that could reach it
ever existed. The counter difference is again **1**. Together the two observations give a
**total unowned counter diff of 2** — this number is the same **diff 2** as the final line
in the measurement core's full run; here it is only arrived at by summing two separate
unowned observations.

#### When a Leak Repeats

The two observations in the previous section measured the leak **once**: each raised the
counter difference by one. But the claim that "in a long-running process, the counter keeps
growing" has so far only been stated in words, not measured. Running the same leaking
mechanism a few times inside a loop turns that claim directly into a number.

```c
// repeat.c — the same leak repeated in a loop
#include <stdio.h>
#include "owner.h"
#include "counter.h"

static int diff(void) {
    return allocated_count() - freed_count();
}

static void leaking_call(const Record *r) {
    /* the return value is deliberately not held: every call leaks a block */
    record_name_copy(r);
}

int main(void) {
    char buffer[64];
    Record *r = record_create("loop", 16);
    int before = diff();

    for (int i = 1; i <= 3; i++) {
        leaking_call(r);
        int now = diff() - before;
        snprintf(buffer, sizeof buffer, "counter diff after call %d is %d", i, now);
        printf("%-30s%s\n", "leak repetition", buffer);
    }

    record_destroy(r);
    return 0;
}
```

```
leak repetition               counter diff after call 1 is 1
leak repetition               counter diff after call 2 is 2
leak repetition               counter diff after call 3 is 3
```

**DM10.** Each `leaking_call` call allocates a block through `record_name_copy` and never
holds the return — just like the `ignored return` observation, but here three calls in a
row instead of one. The counter difference rises by exactly **1** per call: 1, 2, 3. These
three numbers show that the sentence "in a short-lived program this difference may stay
negligible; in a long-running process the counter keeps growing" is now an observation, not
a claim. The loop stops after three iterations, so the difference stops at three; in a real
process, a call like this may run thousands of times per second, and the same linear growth
continues without stopping — the counter is visible here only because it is held by hand in
this course, it is not visible in a real program, but it is still **there**.

#### A Partial Leak: A Half-Freed Array

The previous two observations either leaked completely or not at all — a call's return was
either held or never held. But real code also has a sneakier pattern: **most** of an array
of blocks is freed correctly, one is **skipped**. The total counter difference catches this
with the same sensitivity — independent of how many were done correctly, it only counts how
many are **missing**.

```c
// partial.c — three of four copies freed, one skipped
#include <stdio.h>
#include "owner.h"
#include "counter.h"

static int diff(void) {
    return allocated_count() - freed_count();
}

int main(void) {
    char buffer[80];
    enum { COUNT = 4 };
    char *copies[COUNT];
    Record *r = record_create("partial", 16);
    int before = diff();

    for (int i = 0; i < COUNT; i++) {
        copies[i] = record_name_copy(r);
    }

    for (int i = 0; i < COUNT; i++) {
        if (i == 2) {
            continue; /* the third copy is deliberately not freed */
        }
        count_free(copies[i]);
    }

    int remaining_diff = diff() - before;
    record_destroy(r);

    snprintf(buffer, sizeof buffer,
             "allocated %d, freed %d, remaining counter diff %d", COUNT, COUNT - 1, remaining_diff);
    printf("%-30s%s\n", "partial free", buffer);

    return 0;
}
```

```
partial free                  allocated 4, freed 3, remaining counter diff 1
```

**DM11.** The loop allocated four copies, the second loop freed three of them and skipped
only the copy where `i == 2`. `remaining_diff` comes out to **1** — exactly as many as the
skipped copy, no more, no less. This observation shows that a leak is not "all or nothing":
three of the four free calls in a loop working correctly does not hide the fourth's absence,
the counter difference still grows by exactly the missing amount. In a real program, this
pattern shows up in a branch a loop skips early with a `continue` or a `break` — while the
rest of the code applies a fully correct freeing discipline, a single control-flow branch
falls outside that discipline.

#### The Bounding Measurement

In neither of these two observations did the program **crash**, print any warning, or have
any function return `NULL`. The allocation functions fulfilled their contract completely:
they gave a block of the requested size. The problem is not on their side, it is on the
caller's side — an assignment was forgotten, or a return value was ignored. This is why a
memory leak gives **no observable symptom** at runtime; only the gap between what was
allocated and what was freed grows. In a short-lived program this difference may stay
negligible; if the same mechanism is run over and over in a long-running process, the
counter keeps growing and eventually memory runs out.

**This does not overlap with the lifetime question measured in M10/K02.** There, the
question was where a value's lifetime ends, and that place was decided by the **language** —
a local variable going out of scope was reclaimed automatically. Here there is **no language
rule at all** that ends the block's lifetime; the block is reclaimed not by the language but
only by the **caller's own free call**. If that call is never made, the language ends
nothing on its own — which is why, unlike the other three, the unowned class is the name of
an **absence**, not of a rule.

Each of the other three classes gives an **answer** to who the owner is: in the language
class the owner is the scope itself, in the caller class the owner is the calling side's
code, in the callee class the owner is the contract inside the module. The unowned class
gives no answer — because the question was never asked. In the `overwritten pointer`
observation, the block belonged to the **caller** class the moment it was allocated; the
pointer `count_alloc` returned was not ambiguous about whose responsibility it was. The
ambiguity was born afterward, when that pointer was crushed by a second assignment. The
same is true for `ignored return`: `record_name_copy`'s contract is clear (the caller owns
it), but no variable on the caller's side ever took up that role. Unownedness is therefore
not a fourth, **independent** class; it is the erasure, in the code, of the trace of a
previously existing ownership relationship. A block's owner is never designed to be
"unowned" — it is only ever **left** that way.

#### Summary

- A memory leak is born two ways: overwriting a pointer, or ignoring a returned value; the
  memory class this lesson measures is **unowned**.
- A leak produces no error message or crash; its only visible evidence is the **counter
  difference**.
- Each of the two observations raised the counter difference by **1**, for a total of
  **2** — the same number as the difference on the measurement core's binding final line.
- The unowned class is not a language rule, it is an **absence**: the language never makes
  any free call on its own.

#### Next Step

This lesson measured the leak with a **counter difference**, because the core itself was a
counting wrapper. In a real program, such a counter is not held by hand — instead, **a
tool** that does this job is run. The next lesson shows how this lesson's counting wrapper
actually models what such a tool does: **caught** or **missed**, with no other name
appearing.

### Memory Debugging Tools

Source: https://academia.sh/en/courses/c-memory/memory-debugging-tools

Finding a leak by holding a counter by hand is not possible in a real program — a tool that does this is run instead. This lesson shows that the counting wrapper the course has used from the start already models the one thing such a tool does: is there a free for every allocation, or not.

The previous lesson showed that a leak produces no error message or crash, appearing only
as a counter difference. In a real program, no one compares `allocated_count()` with
`freed_count()` by hand — instead **a tool** that does this is run. This lesson's subject is
not such a tool itself; its subject is what that **one thing it does** actually is.
`counter.c`, which the course has used from the start, has in fact been modeling this since
the first lesson.

This is the natural continuation of the previous lesson. The `Memory Leaks` lesson showed
two kinds of the unowned class and measured both with a hand-held counter difference. But in
a real program, no one writes a line like `allocated_count() - freed_count()` inside `main`
and reads the result by hand; as a program grows, tracking by hand which of hundreds of
allocation calls went unmatched is not practical. So a layer that automates this job steps
in — and this lesson's only claim is that what that layer does rests on **no more
complicated an idea** than the counting wrapper the course has used from the start.

#### What a Checkpoint Compares

What a tool does is, at its core, a single operation: at a given point in the program
(usually at exit), it counts whether there is a matching free call for every allocation call
made up to that point. If a match exists, the check comes out **clean**; if not, a finding
is reported for every block whose match could not be found. In this course, this comparison
is the difference between the two numbers `counter.c` keeps — `allocated_count()` minus
`freed_count()`. If the difference is zero, it is **missed**; if it is nonzero, it is
**caught**.

```c
// counter.h — a wrapper that counts every allocation and every free
#ifndef COUNTER_H
#define COUNTER_H

#include <stddef.h>

void *count_alloc(size_t size);
void *count_grow(void *old, size_t size);
void count_free(void *block);
int allocated_count(void);
int freed_count(void);

#endif
```

```c
// counter.c — the counters live only in this unit
#include <stdlib.h>
#include "counter.h"

static int allocated = 0;
static int freed = 0;

void *count_alloc(size_t size) {
    void *block = malloc(size);
    if (block != NULL) {
        allocated++;
    }
    return block;
}

void *count_grow(void *old, size_t size) {
    int new_block = (old == NULL);
    void *grown = realloc(old, size);
    if (grown != NULL && new_block) {
        allocated++;
    }
    return grown;
}

void count_free(void *block) {
    if (block != NULL) {
        freed++;
    }
    free(block);
}

int allocated_count(void) {
    return allocated;
}

int freed_count(void) {
    return freed;
}
```

**DM12.** `counter.c` has stood unchanged since this course's first lesson and carries the
word "tool" nowhere — only two counters and four functions. But what it does is nothing
more than comparing what was allocated with what was freed at a checkpoint; this is the
scaled-down, hand-built version of what a real tool does. The difference is that a real tool
does this comparison without ever touching the program's source code; here the same
comparison was written **by hand** into `counter.c`.

#### A Balanced and an Unbalanced Sequence

```c
// check.c — a checkpoint distinguishing a balanced sequence from an unbalanced one
#include <stdio.h>
#include "counter.h"

static const char *check(int diff) {
    return diff != 0 ? "caught" : "missed";
}

static void observe(const char *name, const char *evidence) {
    printf("%-30s%s\n", name, evidence);
}

int main(void) {
    char buffer[64];
    int before, diff_after;

    printf("%-30s%s\n", "observation", "evidence");

    /* --- balanced sequence: every allocation has a match --- */
    before = allocated_count() - freed_count();
    int *first = count_alloc(2 * sizeof *first);
    int *second = count_alloc(3 * sizeof *second);
    count_free(first);
    count_free(second);
    diff_after = (allocated_count() - freed_count()) - before;
    snprintf(buffer, sizeof buffer, "checkpoint: %s", check(diff_after));
    observe("balanced sequence", buffer);

    /* --- unbalanced sequence: one allocation has no match --- */
    before = allocated_count() - freed_count();
    int *third = count_alloc(2 * sizeof *third);
    int *fourth = count_alloc(3 * sizeof *fourth);
    count_free(third);
    /* fourth is deliberately not freed: the unbalanced sequence is built here */
    diff_after = (allocated_count() - freed_count()) - before;
    snprintf(buffer, sizeof buffer, "checkpoint: %s", check(diff_after));
    observe("unbalanced sequence", buffer);

    count_free(fourth);
    return 0;
}
```

```
observation                   evidence
balanced sequence             checkpoint: missed
unbalanced sequence           checkpoint: caught
```

**DM13.** In the `balanced sequence` observation, both allocations were freed inside their
own block; the difference at the checkpoint is **0** and the result is **missed**. In the
`unbalanced sequence` observation, `fourth` was not freed by the time it reached that
checkpoint — the code did this itself, marking it with a comment, because that is the only
defined way to show what a tool actually finds. The difference rose to **1** and the result
is **caught**. After the checkpoint, `fourth` is still freed regardless — the lesson does
not leave an undefined state (ending the program with a leak) behind it.

#### Three Sequential Checkpoints

The two observations in the previous section built two **independent** sequences — each
started from its own `before` value. In a real program, checkpoints do not stand isolated
like this; they come one after another, and each sees only the span **since the point
before it**. This shows that the checkpoint where a leak is caught is the exact same span
as where the leak occurred — not the point before it, not the point after it.

```c
// sequence.c — a leak caught exactly at the checkpoint where it occurred
#include <stdio.h>
#include "counter.h"

static const char *check(int diff) {
    return diff != 0 ? "caught" : "missed";
}

int main(void) {
    char buffer[64];
    int point_before = allocated_count() - freed_count();

    /* --- first point: a clean allocate-free pair --- */
    int *a = count_alloc(sizeof *a);
    count_free(a);
    int diff1 = (allocated_count() - freed_count()) - point_before;
    snprintf(buffer, sizeof buffer, "point 1: %s", check(diff1));
    printf("%-30s%s\n", "sequential checkpoint", buffer);
    point_before = allocated_count() - freed_count();

    /* --- second point: a block deliberately not freed --- */
    int *b = count_alloc(sizeof *b);
    int diff2 = (allocated_count() - freed_count()) - point_before;
    snprintf(buffer, sizeof buffer, "point 2: %s", check(diff2));
    printf("%-30s%s\n", "sequential checkpoint", buffer);
    point_before = allocated_count() - freed_count();

    /* --- third point: independent of b, a new pair balanced on its own --- */
    int *c = count_alloc(sizeof *c);
    count_free(c);
    int diff3 = (allocated_count() - freed_count()) - point_before;
    snprintf(buffer, sizeof buffer, "point 3: %s", check(diff3));
    printf("%-30s%s\n", "sequential checkpoint", buffer);

    count_free(b);
    return 0;
}
```

```
sequential checkpoint         point 1: missed
sequential checkpoint         point 2: caught
sequential checkpoint         point 3: missed
```

**DM14.** `b` is allocated at the second point and never freed by the time that point is
reached, which is why **point 2** returns `caught`. The third point builds a new pair (`c`)
that is entirely independent of `b` and balanced on its own — within its own span there is a
free for an allocation, so **point 3: missed**. `b`'s leak is never re-reported anywhere
after the second point; the third point does not count it again, because each point only
sees the unmatched allocations **within its own span**, it does not rescan a span already
reported before. Together, these three results show something important: a leak is caught
at exactly the **same span** in which it occurred in real time — not at the point before it
(it had not been allocated yet) and not at the point after it (that is not that span's
subject). `b` is eventually freed with `count_free` too, but this free is not inside any
point's measured span — the code does this only so as not to end the program with a leak,
not as part of the checkpoint narrative.

#### A Deferred Free Can Mask a New Leak

The mechanism the three sequential points showed rested on an assumption: the allocation and
free calls within each span belonged to that span's **own** events. But in a real program a
block can be allocated at one checkpoint and freed at another — and this can lead the
counter difference to numerically balance out and hide **another** leak occurring in the
same span. This is not a flaw of counter-based checking, it is its **limit**: the check only
sees the net difference, it does not see which block belongs to which event.

```c
// deferred.c — a free carried over from a previous span masking a new leak
#include <stdio.h>
#include "counter.h"

static const char *check(int diff) {
    return diff != 0 ? "caught" : "missed";
}

int main(void) {
    char buffer[64];
    int point_before = allocated_count() - freed_count();

    /* --- point A: d is allocated, not freed in this span at all --- */
    int *d = count_alloc(sizeof *d);
    int diffA = (allocated_count() - freed_count()) - point_before;
    snprintf(buffer, sizeof buffer, "point A: %s", check(diffA));
    printf("%-30s%s\n", "deferred free", buffer);
    point_before = allocated_count() - freed_count();

    /* --- point B: a new leak (e) forms, but d is also freed here --- */
    int *e = count_alloc(sizeof *e);
    count_free(d);
    int diffB = (allocated_count() - freed_count()) - point_before;
    snprintf(buffer, sizeof buffer, "point B: %s", check(diffB));
    printf("%-30s%s\n", "deferred free", buffer);

    count_free(e);
    return 0;
}
```

```
deferred free                 point A: caught
deferred free                 point B: missed
```

**DM15.** Point A correctly reports that `d` was not freed in that span, as **caught** —
just like point 2 in the previous section. At point B, two things happen at the same time:
`e` is newly allocated and **never freed** in this span, and also `d` (a debt carried over
from the previous span) is freed exactly in this span. The counter difference does not see
these two events separately, it only sees their sum: `+1` (newly allocated `e`) and `-1`
(the late-freed `d`) cancel each other out, and `diffB` comes out to **0** — the result is
**missed**, even though a new block (`e`) really has leaked in this span. `e` is only freed
at the end of `main`, at a point no checkpoint measures. This shows that a counter
difference being **zero** does not mean nothing went wrong in that span — it only means what
was allocated and what was freed became numerically equal. Most real tools get past this
limit not just by counting but by tracking each block's own identity (which line it was
allocated on); this course does not track such an identity, because its subject is not the
tool itself, but the underlying idea the tool rests on.

#### The Bounding Measurement

A checkpoint only **counts** — it does not know which line a block was allocated on, which
part of the program that block belongs to, or what the block's content is. All it knows is
the **count** of allocation calls whose match could not be found. This is why, in this
course, a tool's answer is written as only one of two values: **caught** or **missed**. No
output format, no flag, and no tool name appears in this lesson outside this binary —
because which tool produces which output depends on the environment, not on the mechanism
being measured.

What a tool **cannot find** also matters: a checkpoint cannot know whether a pointer's old
value was saved somewhere before it was overwritten, or whether a block's ownership contract
matches the comment in `owner.h` — these require reading the source code. A checkpoint only
verifies the **number itself**; it does not explain why the number reached that value.

The reason the tool's name and output format never appear in this lesson is not just care —
it is a requirement. The same checking idea can be implemented with different tools in
different environments, with different output formats — some run the program inside a
separate process, some replace the allocation functions themselves, some only print a
summary at exit. Since this course cannot know which environment it will run in, it reduces
what it measures to a single environment-independent mechanism: a number, and whether that
number is zero. `caught` and `missed` are therefore not a simplification, they are the
lesson's **actual subject** itself.

#### Summary

- The one thing a memory debugging tool does is compare what was allocated with what was
  freed at a checkpoint; the memory class this lesson measures is again **unowned**.
- `counter.c` has been doing this comparison by hand since the course's first lesson; this
  lesson names it for the first time.
- A checkpoint returned **missed** for a balanced sequence and **caught** for an
  unbalanced one; there is no third result besides these two.
- No tool name, output format, or flag appeared in this lesson — these depend on the
  environment, not on the mechanism being measured.

#### Next Step

This lesson followed memory **moving between modules** — whose hand an allocated block sits
in, how that changes up to a checkpoint. The Dynamic Memory topic's last lesson goes back to
the very beginning: it puts the four `language` observations side by side with the
`allocate and free` observation, and shows how **which region** a block sits in — automatic,
static, or dynamic — lines up with the ownership question.

### Memory Layout

Source: https://academia.sh/en/courses/c-memory/memory-layout

A program's memory is not one single region; it is split into separate areas — automatic, static, and dynamic — and which region a block sits in decides from the start who its owner will be. This lesson lines up the four language observations side by side with the allocate-and-free observation and measures this distinction without printing an address.

The Dynamic Memory topic's previous four lessons stayed in the **dynamic** region of memory:
`malloc`, `calloc`, `realloc`, the ownership contract, the leak, the checkpoint — all of it
concerned blocks allocated at runtime and freed by hand. But since this course's very first
lesson, every measurement has also carried the **language** class: local variables, static
areas, string literals. As it closes the topic, this lesson puts the dynamic region **next
to** the remaining three regions and shows how which region a block sits in decides from the
start who its owner will be.

#### Four Regions, One Question

A C program's memory is split into several regions: the code itself, constant data, static
variables, the runtime stack (for automatic variables), and the heap (for dynamic
allocation). How these regions are laid out in hardware and how the boundaries between them
are aligned was covered in the How Computers Work course (M01/K01); this lesson does not
retell that layout, it only relates it to the **ownership** question. The question is always
the same: who will free this block? The answer depends on which region the block sits in.

```c
// counter.h — a wrapper that counts every allocation and every free
#ifndef COUNTER_H
#define COUNTER_H

#include <stddef.h>

void *count_alloc(size_t size);
void *count_grow(void *old, size_t size);
void count_free(void *block);
int allocated_count(void);
int freed_count(void);

#endif
```

```c
// counter.c — the counters live only in this unit
#include <stdlib.h>
#include "counter.h"

static int allocated = 0;
static int freed = 0;

void *count_alloc(size_t size) {
    void *block = malloc(size);
    if (block != NULL) {
        allocated++;
    }
    return block;
}

void *count_grow(void *old, size_t size) {
    int new_block = (old == NULL);
    void *grown = realloc(old, size);
    if (grown != NULL && new_block) {
        allocated++;
    }
    return grown;
}

void count_free(void *block) {
    if (block != NULL) {
        freed++;
    }
    free(block);
}

int allocated_count(void) {
    return allocated;
}

int freed_count(void) {
    return freed;
}
```

```c
// layout.c — four language observations against an allocate-and-free contrast
#include <stdio.h>
#include <string.h>
#include "counter.h"

static int fixed_static = 3;

static int diff(void) {
    return allocated_count() - freed_count();
}

static void observe(const char *name, const char *class, const char *evidence) {
    printf("%-30s%-10s%s\n", name, class, evidence);
}

int main(void) {
    char buffer[64];
    int before;

    printf("%-30s%-10s%s\n", "observation", "owner", "evidence");

    /* --- language: automatic region, reclaimed on its own when scope ends --- */
    before = diff();
    int local = 5;
    int *p = &local;
    snprintf(buffer, sizeof buffer, "*p %d, counter diff %d", *p, diff() - before);
    observe("local variable", "language", buffer);

    /* --- language: static region, sits in one place for the program's whole life --- */
    int *s = &fixed_static;
    snprintf(buffer, sizeof buffer, "*s %d, no free call", *s);
    observe("static field", "language", buffer);

    /* --- language: constant data region, immutable text --- */
    const char *literal = "fixed";
    snprintf(buffer, sizeof buffer, "length %d, no free call", (int)strlen(literal));
    observe("string literal", "language", buffer);

    /* --- language: an array living in the automatic region --- */
    int array[4] = {1, 2, 3, 4};
    int *first = array;
    int *last = &array[3];
    snprintf(buffer, sizeof buffer, "last - first %d, *last %d", (int)(last - first), *last);
    observe("local array", "language", buffer);

    /* --- caller: heap region, allocated by hand and freed by hand --- */
    before = diff();
    int *block = count_alloc(4 * sizeof *block);
    int total = 0;
    if (block != NULL) {
        for (int i = 0; i < 4; i++) {
            block[i] = i + 1;
            total += block[i];
        }
    }
    count_free(block);
    snprintf(buffer, sizeof buffer, "total %d, counter diff %d", total, diff() - before);
    observe("allocate and free", "caller", buffer);

    return 0;
}
```

```
observation                   owner     evidence
local variable                language  *p 5, counter diff 0
static field                  language  *s 3, no free call
string literal                language  length 5, no free call
local array                   language  last - first 3, *last 4
allocate and free             caller    total 10, counter diff 0
```

**DM16.** In all four `language` observations, the counter never moved at all — `local
variable` and `local array` sit in the automatic region, `static field` sits in the static
region, `string literal` sits in the constant data region; all three are reclaimed by the
**program itself**, by scope or by the program's lifetime. The `allocate and free`
observation sits in the heap region and the counter difference again comes out **0** — but
this time the zero comes not from something the language does on its own, but from
`count_free` written to match `count_alloc`. The same result (**diff 0**) comes from two
different reasons: one is zero because it was never measured, the other is zero because it
was measured and balanced out.

#### The Region Determines the Owner, the Owner Does Not Determine the Region

Which region a block sits in is, most of the time, clear from **the declaration itself**:
writing `int local = 5;` inside a function places it in the automatic region, declaring it
at file scope or with the `static` keyword places it in the static region, writing a string
literal places it in the constant data region, and calling `count_alloc` (or `malloc`
directly) places it in the heap region. The ownership class **derives** from this
declaration: no block in the automatic or static region ever receives a free call, its owner
is always the **language**; every block in the heap region expects a free call, its owner is
the **caller** (or, as seen in the `Deallocation Discipline` lesson, the **callee**, per the
contract). The region alone is not enough — whether a block in the heap region belongs to
the caller or the callee is not told by the region itself, that is decided by a contract like
`owner.h`. But for the automatic and static region, the region alone **is** enough: no block
that lands there is ever freed by hand.

#### A Nested Scope Is Also in the Language Class

The four `language` observations above were all in `main`'s outermost scope. But the
automatic region's rule does not look at how **deep** the scope is — every `{ }` block
inside a function body carries its own automatic lifetime, and a nested block is in the
language class just as much as the outer one. At the same time, it is also possible to show
that a block's **size** growing on the dynamic side does not change its ownership class:
whether it is four elements or forty, every block returned by `count_alloc` still belongs to
the caller.

```c
// nested.c — nested automatic scope against a growing heap block
#include <stdio.h>
#include "counter.h"

static int diff(void) {
    return allocated_count() - freed_count();
}

static void observe(const char *name, const char *class, const char *evidence) {
    printf("%-30s%-10s%s\n", name, class, evidence);
}

int main(void) {
    char buffer[64];
    int before;

    /* --- language: an automatic variable inside a nested block --- */
    int outer = 1;
    {
        int inner = 2;
        {
            int innermost = 3;
            snprintf(buffer, sizeof buffer,
                     "outer %d, inner %d, innermost %d, counter diff %d",
                     outer, inner, innermost, diff());
            observe("three nested scopes", "language", buffer);
        }
    }

    /* --- caller: a heap block growing tenfold still belongs to the caller --- */
    before = diff();
    enum { SMALL = 4, LARGE = 40 };
    int *small_block = count_alloc(SMALL * sizeof *small_block);
    int *large_block = count_alloc(LARGE * sizeof *large_block);
    int total = 0;
    if (small_block != NULL && large_block != NULL) {
        for (int i = 0; i < SMALL; i++) {
            total += small_block[i] = i;
        }
        for (int i = 0; i < LARGE; i++) {
            total += large_block[i] = i;
        }
    }
    count_free(small_block);
    count_free(large_block);
    snprintf(buffer, sizeof buffer, "total %d, counter diff %d", total, diff() - before);
    observe("small and large block", "caller", buffer);

    return 0;
}
```

```
three nested scopes           language  outer 1, inner 2, innermost 3, counter diff 0
small and large block         caller    total 786, counter diff 0
```

**DM17.** In the `three nested scopes` observation, three variables were declared at three
separate `{ }` depths, but all three are in the automatic region and none of them touched
the counter at all — the depth of the scope does not change whether the language class
applies. The `small and large block` observation allocates and frees two heap blocks with
sizes ten times apart (4 and 40 elements) side by side; the counter difference again comes
out to **0**, but this time from two separate `count_alloc`/`count_free` pairs balancing
out. This tests the claim in the `The Region Determines the Owner` section against depth and
size: what decides whether a block is automatic or heap is neither how many scopes deep it
is nor how many bytes it holds — what decides it is only **which declaration** it came into
being through. `int innermost` is in the language class even at three scope depths;
`count_alloc(40 * sizeof(int))` is in the caller class even in a single call.

#### Same Class, Different Persistence

No block in the `language` class is ever freed by hand — but this does not mean that every
region inside the `language` class behaves the **same** way. The automatic region starts
fresh on every call; the static region is **persistent** across calls. Both give the same
answer to the ownership question (owner is the language, no free call), but their lifetimes
are entirely different from each other.

```c
// persistent.c — a static variable inside a function persisting across calls
#include <stdio.h>
#include "counter.h"

static int diff(void) {
    return allocated_count() - freed_count();
}

static int keep_count(void) {
    static int call = 0;
    call++;
    return call;
}

static int reset_locally(void) {
    int value = 0;
    value++;
    return value;
}

int main(void) {
    char buffer[96];
    int before = diff();

    int first = keep_count();
    int second = keep_count();
    int third = keep_count();

    int automatic_first = reset_locally();
    int automatic_second = reset_locally();

    snprintf(buffer, sizeof buffer,
             "static calls %d %d %d, automatic calls %d %d, counter diff %d",
             first, second, third, automatic_first, automatic_second, diff() - before);
    printf("%-30s%s\n", "persistent versus reset", buffer);

    return 0;
}
```

```
persistent versus reset       static calls 1 2 3, automatic calls 1 1, counter diff 0
```

**DM18.** The variable `call` inside `keep_count` is declared `static`, so it sits in the
static region and **carries** the same value across three calls: 1, 2, 3. The variable
`value` inside `reset_locally` sits in the automatic region, is redeclared on every call, and
starts from `0`; both calls return **1**, because the second call does not continue from
where the first left off. The counter difference is still **0**, because no heap allocation
happened at all — this measurement shows not the ownership class but the **lifetime**
difference within the same class. The rule from the `The Region Determines the Owner, the
Owner Does Not Determine the Region` section still holds here: the `static` keyword moves a
variable from the automatic region to the static region, and that move stays inside the
`language` class, which still requires no free call — but it changes persistence from the
ground up.

#### The Bounding Measurement

Where the boundaries between regions sit and which alignment value a block is placed
according to depend on the runtime environment; this lesson prints neither an address nor an
alignment value. The `last - first` difference came out to **3** — this is not the addresses
themselves but the **element count** between them, and it is a measure independent of the
region boundary, a portable one.

The automatic region's size is not unlimited either — every call frame takes up a bounded
place in that region, and nested scopes (like the one in the `three nested scopes`
observation) or a deep recursion can exhaust that place. Crossing this bound is undefined
behavior (commonly known as **stack overflow**), and, following this course's rule on
undefined behavior for the rest of the topic, this lesson does **not run** it — it only
states that it exists. The heap region's bound shows up in a different form: when
`count_alloc` cannot meet the requested size, it does not fail silently, it returns `NULL`
in keeping with `malloc`'s contract, and every allocation throughout this lesson was checked
against that return (`small_block != NULL && large_block != NULL`). The automatic region
overflowing shows itself with a crash, the heap region filling up shows itself with a `NULL`
return — both are proof that these regions are **not unlimited**, but one produces a
checkable return value, the other does not.

#### Summary

- Memory is split into four regions: automatic, static, constant data, and heap; the memory
  class this lesson measures is **language**.
- Blocks in the automatic and static region are never freed by hand — the owner is the
  language, the counter never moves.
- Blocks in the heap region are balanced with `count_alloc`/`count_free`; the counter
  difference can again come out to **0**, but this time because it was measured and
  balanced.
- The region alone decides the ownership class for automatic and static memory; in the heap
  region, what decides the class is not the region but the contract.
- Region boundaries and alignment values depend on the environment; this lesson does not
  print them, that is left to M01/K01.

#### Next Step

The Dynamic Memory topic closes here: each of the four ownership classes (language, caller,
callee, unowned) has been seen with at least one measurement. The next topic, Robustness,
takes up the moment the contract **breaks** — what happens when a rule is written down and
then knowingly violated, which situations are only described in prose and never run, and
which defined counterpart runs alongside them.

## Robustness

Why undefined behavior exists and how its defined counterpart is written instead, reporting errors with return codes and error numbers, bounds checking where the caller supplies the length, and isolating implementation-defined behavior from portable code.

### Undefined Behavior

Source: https://academia.sh/en/courses/c-memory/undefined-behavior

Reading a dangling pointer, using freed memory, freeing a block twice, and accessing an array out of bounds are all undefined behavior; none of them run — the lesson describes each one in prose and runs its defined counterpart alongside it.

The previous lesson gathered four regions on a single axis: the stack, static storage, the
string literal, and the heap — the common point was that who decides its lifetime is written
in the source. This lesson measures the moment that contract **breaks**. The contract used
to answer the question "who will free this block"; when the contract breaks, what appears is
not an error message but **undefined behavior** — and the most important property of
undefined behavior is that it cannot be measured.

This last sentence is not a warning, it is a definition that draws the lesson's boundary.
The four sections below describe four separate sources of undefined behavior — a dangling
pointer, freed memory, a double free, an out-of-bounds access — but none of them run the
source itself. The code running alongside each section is the **defined counterpart that
replaces that source**: a check line inserted before the contract breaks. This distinction
holds throughout the course — what is measured is always defined behavior — but it matters
especially in this lesson, because the name of the unmeasured side is also stated explicitly
in the prose.

#### Why Undefined Behavior Exists

The C standard does not define the meaning of every expression. Some expressions are
**defined**: `int total = a + b;` always works by the same rule. Some are
**implementation-defined**: the compiler makes a choice and documents that choice. But some
expressions are **undefined**: the standard says nothing at all about what happens — the
compiler may behave however it likes, or not behave at all.

This gap is not an oversight, it is a design decision. The compiler optimizes by
**assuming that a program never enters undefined behavior**. For example it may assume that
a pointer always points to a valid block, because pointing to an invalid block is itself
undefined and the standard gives no guarantee in that case. This assumption lets the
compiler drop unnecessary checks, reorder operations, and cache a value instead of
recomputing it. If the assumption turns out wrong — that is, the program really does enter
undefined behavior — the generated code still compiles and still does **something**, but
what that something is is not the standard's concern.

The effect of this assumption changes with the compiler version, the optimization level, and
even the shape of the surrounding code; the same undefined expression may pass unnoticed in
one build and send the program down a completely different path in another. The standard
treats both outcomes as equally **valid**, because both fall under the word "undefined".
This is why the way to find undefined behavior is not to run the program and check whether
it produced an error — the absence of an error proves nothing, it only shows that nothing
was observed in that **particular** run, with that **particular** compiler.

Among the measurement core's four classes, this lesson's subject is **unowned** memory: once
the contract breaks, neither the language, nor the caller, nor the callee can reach that
block through a defined path any longer — the block may still sit there, but no party is
left to use it safely. That is why none of the four sources below answers the question
"who owns it" directly; all of them show the moment **after** the ownership contract has
already been broken.

This is why "looking like it works" carries no proof. The next section takes up, one by one,
the four sources this repository's measurement core deliberately never runs; each one first
explains in prose why the contract breaks, then runs a **defined** counterpart in its place.

#### Reading a Dangling Pointer

Once a block is freed, the pointer that pointed to it **dangles**: the pointer itself still
holds the old address, but the memory at that address has already been returned to the
allocator. Dereferencing a dangling pointer (`*p`) — even just to read it — is undefined
behavior. The memory may appear to still hold the same content, because the allocator may
not yet have handed that block to another request; that is a **coincidence**, not a
guarantee. The same program may hand that address to a completely different block on the
next run, or after the next allocation request in the same run.

The lesson never runs this read. Instead it runs the defined pattern that **prevents** a
dangling pointer from forming: assigning `NULL` to the pointer the moment a block is freed,
and checking that before every use.

```c
// guard.c — the defined counterpart that prevents a dangling pointer from forming
#include <stdlib.h>

const char *dangling_counterpart(void) {
    int *block = malloc(sizeof *block);
    if (block != NULL) {
        *block = 9;
    }
    free(block);
    block = NULL;
    return (block == NULL) ? "pointer null, no dereference" : "accessed";
}
```

This counterpart's measurement is shown, together with the other three counterparts, in the
lesson's single closing code block that contains `main`.

The point to note is this: pulling `block` to `NULL` changes the pointer **itself**, not the
memory it pointed to — that memory already returned to the allocator with the `free` call.
The check only prevents that same pointer variable from being used again; if **another**
pointer holds the same address (for example a second variable pointing at the same block),
that second pointer still dangles and this pattern does not protect it. The pattern's scope
is limited exactly to the pointer variable it checks.

#### Using Freed Memory

Using freed memory (use-after-free) is a little broader than reading a dangling pointer: it
also covers **writing** to the block. Writing `*p = value;` to the same block after a `free`
call may mean the allocator has already reopened that memory region for reuse — the write
may land on bytes that now belong to the allocator's own bookkeeping and corrupt the
allocator's internal consistency. This too is undefined and may not produce an error
message; the program may keep running "normally" for a while.

Its defined counterpart is to carry a **validity flag** tied to the block: the flag drops
when the block is freed, and every write checks that flag.

```c
// validity.c — the pattern that uses a flag to block writes after freeing
#include <stdlib.h>

const char *use_counterpart(void) {
    int *second = malloc(sizeof *second);
    int valid = (second != NULL);
    if (valid) {
        *second = 4;
    }
    free(second);
    second = NULL;
    valid = 0;
    if (valid) {
        *second = 7;
    }
    return valid ? "written" : "not written, flag dropped";
}
```

This counterpart's measurement is also shown in the same closing code block. Alongside the
flag, the pointer itself is also pulled to `NULL` right after `free`. This line is not
decoration: without it, a build with warnings enabled sees that `second` still appears in the
text after `free` and prints a warning — even though the branch never runs, because the
compiler cannot always prove that a branch will not run. Even the compiler itself keeps
tracking a freed pointer in its own view; the flag closes off the branch, pulling it to
`NULL` keeps the name from carrying a freed address, and together the two let the block
compile without warnings.

The flag's placement also matters: the flag must share the **same lifetime** as the pointer
it protects. If the flag is a local variable and the pointer passes from one function to
another, the callee cannot know whether the pointer is still valid unless the flag travels
with the pointer — which means the validity check itself carries **ownership separate** from
the block it checks; whose responsibility it is to update the flag is a question of its own.

#### Double Free

Handing the same block to `free` twice is also undefined. Once the allocator frees a block,
it adds that address to its own ledger (the free list); a second `free` call tries to add
the same address to that ledger a second time and can corrupt the ledger's internal
structure. The result may show up on the allocator's next `malloc` call, or it may never
show up — neither is something the standard says anything about.

Its defined counterpart is to also mark that a block has been freed with a flag, and
**skip** the second free request using that flag.

```c
// single_free.c — the pattern that uses a flag to prevent a double free
#include <stdlib.h>

const char *single_free(void) {
    int *third = malloc(sizeof *third);
    int freed = 0;

    if (!freed) {
        free(third);
        freed = 1;
    }
    if (!freed) {
        free(third);
        freed = 1;
    }
    return freed ? "freed once" : "not freed";
}
```

The second `freed` check here **never runs** — it exists as a line of code, but the `freed`
flag is already true at that point, so the condition never holds. This is the concrete form
of the rule set up at the start of the lesson: a line that shows the undefined path may sit
in the source, as long as it is never executed. Its measurement is also in the closing code
block.

Seen from the ownership angle, a double free is an interesting case: the moment a block is
freed, its ownership has already passed to **nobody** — neither the language, nor the
caller, nor the callee owns it any longer. The second `free` call is a call into that gap;
the allocator does not recognize that address, or recognizes it wrongly, because it no
longer holds a **valid** record of that address. The single-free flag does not fill this
gap, it only prevents entering it a second time.

#### Out-of-Bounds Access

Reaching outside an array's `[0, length)` range — `array[length]` or further — is undefined.
The memory may physically hold something there, but that byte is not part of the array; the
compiler may drop bounds checks by assuming an array access always stays within bounds. An
out-of-bounds read yields an arbitrary value, an out-of-bounds write may corrupt another
variable's data.

Its defined counterpart is to **compare the requested index against the known length**
before the access.

```c
// bounded.c — the pattern that compares an index against a length to keep access defined
const char *bounded_access(int requested) {
    int array[4] = {10, 20, 30, 40};
    int length = 4;
    if (requested >= 0 && requested < length) {
        (void)array[requested];
        return "accessed";
    }
    return "rejected, out of bounds";
}
```

The `requested < length` check does not exist in C itself — an array access (`array[i]`)
always silently permits it in the language, with no check at either run time or compile
time. The check is entirely a line **the programmer adds**; without it, an index past the
end of the array compiles and enters undefined behavior.

This shows a point that differs from the other three sources: a dangling pointer and freed
memory are a **timing** problem — a block is valid at one point, then invalid later.
Out-of-bounds access is independent of timing; even when the array was never freed and is
still fully valid, the wrong index still enters undefined behavior. The common point is that
in both cases the compiler adds **no** run-time check at all — C itself tracks neither the
timing nor the index.

#### Running All Four Counterparts Together

None of the four is undefined; all four are patterns that **prevent** undefined behavior,
and all four run in a single `main`, in sequence.

```c
// measurement.c — all four defined counterparts measured together in one main
#include <stdio.h>

const char *dangling_counterpart(void);
const char *use_counterpart(void);
const char *single_free(void);
const char *bounded_access(int requested);

static void observe(const char *name, const char *result) {
    printf("%-28s%s\n", name, result);
}

int main(void) {
    printf("%-28s%s\n", "observation", "result");
    observe("dangling pointer", dangling_counterpart());
    observe("freed memory", use_counterpart());
    observe("double free attempt", single_free());
    observe("valid index", bounded_access(2));
    observe("out-of-bounds index", bounded_access(5));
    return 0;
}
```

```text
observation                 result
dangling pointer            pointer null, no dereference
freed memory                not written, flag dropped
double free attempt         freed once
valid index                 accessed
out-of-bounds index         rejected, out of bounds
```

**RO1.** The `block == NULL` check here **prevents** the dangling pointer, it does not
measure the dangling pointer itself. Undefined behavior has no measurable "result"; the only
thing that can be measured is proof that it was never entered — that is why the output line
says "no dereference", not "the value came out as such".

**RO2.** The flag itself is an ordinary `int`; C has no separate "validity" type. The
guarantee comes not from the type but from the **discipline of every write site checking the
flag** — the compiler does not enforce this discipline, a line that forgets to check
compiles silently.

**RO3.** The second `freed` check inside `single_free` never ran during this run — the
output gave a single "freed once" line, with no trace of a second free.

**RO4.** The last two lines are the same function called with two different `requested`
values on the same array: the valid index stays inside the array and is accessed, the
out-of-bounds index is rejected without being accessed — both are defined, neither ever
touches past the end of the array.

What all four lines share is that none of them relies on a guarantee from the language or a
runtime. `dangling_counterpart` pulls the pointer to `NULL` with its own hands,
`use_counterpart` drops the flag with its own hands, `single_free` skips the second free with
its own hands, `bounded_access` compares the bound with its own hands — because none of the
four has an **owning party** for that block, the only guarantee left is the check written in
the source. This does not mean that none of the previous lesson's four classes (language,
caller, callee, unowned) is in play here; on the contrary, these four examples are exactly
what makes the **unowned** class concrete: once a block loses its validity at some point,
neither the language, the caller, nor the callee protects it on its own anymore. If there is
protection, it is the line of code that put it there — not the compiler, not the runtime
library, and certainly not the type system. That is why this lesson's counterpart of the
unowned class does not **replace** the previous lesson's language/caller/callee classes; it
separately addresses a weak point common to all of them: the moment right after the contract
breaks.

#### The Bounding Measurement

Undefined behavior is not an error message. The four counterparts above give exactly the
same output on every run — but that consistency comes from the counterparts being
**defined**, it does not show that the undefined operations involved are "actually
harmless". None of this lesson's code blocks dereferences a dangling pointer, writes to a
freed block, frees a block twice, or reaches past an array's end; all of them run the pattern
that **prevents** those things. A program "looking like it works" is not proof that it
contains no undefined behavior — the same source may behave differently when the compiler
version, the optimization level, or the next allocation request changes.

This also sets the lesson's measurement boundary: none of the programs run here shows what
undefined behavior "does", because showing such a thing would already contradict the
concept's own definition. The lesson can only measure two things — describing in prose where
the contract breaks, and running the defined path that replaces the break. The difference
resembles the difference between a lab experiment and a recipe: an experiment's result can be
observed, a recipe only guarantees its result if followed — what happens if it is not
followed is not the recipe's concern.

#### Summary

- Undefined behavior is a gap where the compiler optimizes by **assuming it is never
  entered**; "looking like it works" does not show that it is defined.
- Reading a dangling pointer, using freed memory, freeing a block twice, and reaching past
  an array's bounds — all four are undefined, and **none of them ran** in this lesson.
- Every source has a defined counterpart: a validity flag, a single-free flag, and an
  index-length check — all lines the programmer adds, none enforced by the language.
- The memory this lesson measures is in the **unowned** class: once the contract breaks, no
  party obligated to free the block can reach it through a defined path any longer.

#### Next Step

The contract breaking shows up not only in memory access but also on the path where a
function **returns an error** — something needs to be cleaned up there too, but this time
without entering undefined behavior. The next lesson measures that cleanup through the
callee class's function: return codes, error numbers, and single-exit-point cleanup
patterns.

### Error Handling Patterns

Source: https://academia.sh/en/courses/c-memory/error-handling-patterns

When a function fails, who frees the block it had already allocated up to that point is not a rule the compiler enforces — it is the function's own contract; the lesson measures this by running a return code, an error number, and a single-exit-point cleanup pattern.

The previous lesson took up four moments where the contract breaks, and never ran any of
them. This lesson measures a moment where the contract **does not break**, but the function
**fails**: a function starts allocating memory for a task, a condition partway through does
not hold, and it has to return. The block allocated up to that point is now in nobody's
hands — who frees it follows not from a rule in the source, but from that function's **own
contract**.

The difference between these two lessons is subtle but binding: undefined behavior is the
moment the contract **breaks**, partial failure is a moment the contract is **still standing**
— the function fails, but that failure is not something the standard leaves undefined, it is
a path the function itself anticipated. The difference carries over into the measurement too:
what the previous lesson measured was a block that "no party could protect once the contract
broke"; what this lesson measures is how the party **that wrote the contract** cleans up its
own failure. The pattern proceeds in three stages: a single-resource error path, a
multi-resource error path, and finally the standard library's own error-reporting path.

#### Cleanup on the Error Path

We already saw this example in the measurement core: if the given name does not fit the
`limit` parameter, `record_create` **itself** frees the record it had allocated up to that
point and returns `NULL` to the caller. Nothing is left for the caller to clean up.

This rule may look like it applies to a single block only, but it is actually a **promise**
a function makes to the outside world: a function that returns `NULL` commits to behaving as
if it allocated nothing at all — the caller does not have to call `record_destroy`, because
there is nothing to destroy. This promise is not marked by any keyword in the source; it is
written only in the comment in `owner.h`, and `record_create`'s body keeps that promise. A
function that does not keep the promise — for example one that allocates an intermediate
block, returns `NULL`, and forgets to free that block — produces no warning in the language;
the result is a silent memory leak that only shows up as a counter difference.

This section's measurement is shown, together with the other two patterns, in the lesson's
single closing code block that contains `main`. The measurement core's four files are used in
this lesson exactly as before:

```c
// counter.h — a wrapper that counts every allocation and every free
#ifndef COUNTER_H
#define COUNTER_H

#include <stddef.h>

void *count_alloc(size_t size);
void *count_grow(void *old, size_t size);
void count_free(void *block);
int allocated_count(void);
int freed_count(void);

#endif
```

```c
// counter.c — the counters live only in this unit
#include <stdlib.h>
#include "counter.h"

static int allocated = 0;
static int freed = 0;

void *count_alloc(size_t size) {
    void *block = malloc(size);
    if (block != NULL) { allocated++; }
    return block;
}

void *count_grow(void *old, size_t size) {
    int new_block = (old == NULL);
    void *grown = realloc(old, size);
    if (grown != NULL && new_block) { allocated++; }
    return grown;
}

void count_free(void *block) {
    if (block != NULL) { freed++; }
    free(block);
}

int allocated_count(void) { return allocated; }
int freed_count(void) { return freed; }
```

```c
// owner.h — the record's inside is hidden, the ownership contract is in the comments
#ifndef OWNER_H
#define OWNER_H

#include <stddef.h>

typedef struct Record Record;

/* The caller owns it and frees it with record_destroy. If the name does not
   fit the limit, returns NULL; the function frees what it allocated so far. */
Record *record_create(const char *name, size_t limit);

/* Frees the record together with the name buffer inside it. */
void record_destroy(Record *r);

/* The record owns it: the returned pointer is valid until the record is
   destroyed, and the caller does not free it. */
const char *record_name(const Record *r);

/* The caller owns it: the returned copy is freed with count_free. */
char *record_name_copy(const Record *r);

#endif
```

```c
// owner.c — the unit where the contract is carried out
#include <string.h>
#include "owner.h"
#include "counter.h"

struct Record {
    char *name;
    size_t length;
};

Record *record_create(const char *name, size_t limit) {
    Record *r = count_alloc(sizeof *r);
    if (r == NULL) { return NULL; }

    r->length = strlen(name);
    if (r->length >= limit) {
        count_free(r);
        return NULL;
    }

    r->name = count_alloc(r->length + 1);
    if (r->name == NULL) {
        count_free(r);
        return NULL;
    }
    memcpy(r->name, name, r->length + 1);
    return r;
}

void record_destroy(Record *r) {
    if (r == NULL) { return; }
    count_free(r->name);
    count_free(r);
}

const char *record_name(const Record *r) { return r->name; }

char *record_name_copy(const Record *r) {
    char *copy = count_alloc(r->length + 1);
    if (copy != NULL) { memcpy(copy, r->name, r->length + 1); }
    return copy;
}
```

#### Single-Exit-Point Cleanup for Multiple Resources

`record_create` frees a single block itself because it allocates a single block. If a
function allocates several blocks in sequence and one in the middle turns out invalid, the
ones already allocated must first be **rolled back**. This pattern's name is single-exit-point
cleanup: the function jumps with `goto` to a single `cleanup:` label, and from that label
frees everything allocated up to that point.

`goto` is generally a discouraged construct in C, because it can make control flow hard to
follow. This pattern is the one accepted exception to that general advice: `goto` is used
only **forward**, only to a single label **at the end of the function**, and only for
cleanup. The alternative — repeated code that frees every block allocated so far at each
individual failure point — does the same job, but its line count grows multiplicatively as
the number of allocated blocks grows, and every new resource added requires updating every
previous failure branch. `goto cleanup` instead gathers the rollback logic in **a single
place**; adding a new resource only adds a new `if` and a new allocation line, the `cleanup:`
label itself does not change.

```c
// triple.h — declaration of a three-resource allocation
#ifndef TRIPLE_H
#define TRIPLE_H

#include <stddef.h>

int triple_alloc(size_t a, size_t b, size_t c, size_t limit,
                  int **p1, int **p2, int **p3);

#endif
```

```c
// triple.c — rolling back on partial failure with the goto cleanup pattern
#include <stddef.h>
#include "triple.h"
#include "counter.h"

enum Status { STATUS_OK = 0, STATUS_ERROR = 1 };

int triple_alloc(size_t a, size_t b, size_t c, size_t limit,
                  int **p1, int **p2, int **p3) {
    int status = STATUS_OK;
    *p1 = NULL;
    *p2 = NULL;
    *p3 = NULL;

    if (a > limit) {
        status = STATUS_ERROR;
        goto cleanup;
    }
    *p1 = count_alloc(sizeof **p1);

    if (b > limit) {
        status = STATUS_ERROR;
        goto cleanup;
    }
    *p2 = count_alloc(sizeof **p2);

    if (c > limit) {
        status = STATUS_ERROR;
        goto cleanup;
    }
    *p3 = count_alloc(sizeof **p3);

    return status;

cleanup:
    count_free(*p3);
    count_free(*p2);
    count_free(*p1);
    *p1 = *p2 = *p3 = NULL;
    return status;
}
```

**RO5.** In the first call, `b`'s value (**9**) exceeds the limit (**5**), so with `*p1`
already allocated it will jump to the `cleanup:` label and that one block will be freed back;
in the second call all three stay under the limit, so `cleanup:` is never reached, all three
blocks pass to the caller, and the caller frees them itself. The single `main` below measures
both of these paths in one run, together with `record_create`'s error path and reading
`errno`.

`triple_alloc`'s `cleanup:` label only frees **what was allocated up to that point** — because
the `count_free` function does nothing when called with a `NULL` pointer (the `if (block !=
NULL)` check in `counter.c`), the `count_free(*p2)` and `count_free(*p3)` calls safely no-op
for the not-yet-allocated `*p2` and `*p3`. This explains why the rollback pattern can always
be written with **the same order, the same three calls**: the code does not have to track
separately which block was actually allocated, because `count_free` already carries the
guarantee "do nothing if not allocated". It is `count_free` that carries the guarantee, not
`goto` — `goto` only gathers the flow at a single point.

#### Return Code and Error Number

`triple_alloc` returns an `int`, and that value answers only the question **succeeded or
failed**; it does not say which resource was rejected — if that information is wanted, it has
to travel through a separate parameter. The standard library's own error reporting instead
works through `errno`: when a function fails it sets `errno` to a **nonzero** value, but that
value's own contents are target-dependent and are never printed; only whether it is nonzero
is read.

A return code and `errno` are two different solutions to the same problem, but they do not
operate at the same layer. A return code is **that function's own design** — `triple_alloc`'s
`int` return is a contract chosen by whoever wrote that function, and shares no common rule
with any other function's return value. `errno`, on the other hand, is the standard library's
**shared** channel: `strtol`, file operations, and memory allocation all use the same `errno`
variable — which is why telling apart which call left which error requires resetting it
immediately before every call.

```c
// measurement.c — error path, goto cleanup, and errno measured in one run
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include "owner.h"
#include "counter.h"
#include "triple.h"

static int diff(void) {
    return allocated_count() - freed_count();
}

static void observe(const char *name, const char *result) {
    printf("%-28s%s\n", name, result);
}

int main(void) {
    char buffer[64];

    printf("%-28s%s\n", "observation", "result");

    /* --- cleanup on error path: record_create frees its own allocation --- */
    int before = diff();
    Record *too_long = record_create("a very long record name", 8);
    snprintf(buffer, sizeof buffer, "null %d, counter diff %d", too_long == NULL, diff() - before);
    observe("cleanup on error path", buffer);

    /* --- goto cleanup: rolling back on partial failure --- */
    int *a = NULL, *b = NULL, *c = NULL;
    before = diff();
    int status = triple_alloc(2, 9, 2, 5, &a, &b, &c);
    snprintf(buffer, sizeof buffer, "status %d, counter diff %d", status, diff() - before);
    observe("second resource rejected", buffer);

    before = diff();
    status = triple_alloc(1, 2, 3, 5, &a, &b, &c);
    count_free(a);
    count_free(b);
    count_free(c);
    snprintf(buffer, sizeof buffer, "status %d, counter diff %d", status, diff() - before);
    observe("all three accepted", buffer);

    /* --- errno: only whether it is nonzero is read --- */
    errno = 0;
    char *end = NULL;
    long overflowed = strtol("99999999999999999999", &end, 10);
    (void)overflowed;
    observe("overflowing number parse", errno != 0 ? "error number nonzero" : "error number zero");

    errno = 0;
    long normal = strtol("42", &end, 10);
    (void)normal;
    observe("normal number parse", errno == 0 ? "error number zero" : "error number nonzero");

    return 0;
}
```

```text
observation                 result
cleanup on error path       null 1, counter diff 0
second resource rejected    status 1, counter diff 0
all three accepted          status 0, counter diff 0
overflowing number parse    error number nonzero
normal number parse         error number zero
```

**RO6.** When the `r->length >= limit` check inside `record_create` holds, the function
frees its own allocated `r` with `count_free` and returns `NULL`; the counter difference
coming out as **0** is proof that the one allocated block was freed within the same
function. Before each of the two `strtol` calls, `errno` is explicitly **reset to zero**:
`errno` is only set when there is an error, it is not guaranteed to be reset on success —
resetting it before the check is required so as not to read a value left over from a
previous call.

None of the five rows shown in the table continues from another; each starts
**independently**, with its own `before`/`diff` pair or its own `errno` reset. This separation
is deliberate: running two measurements back to back and interpreting the difference by
reading a single counter could confuse which measurement caused which change. Calling the
`diff()` function before each observation — the `before = diff();` line — prevents this
confusion: each row's counter difference comes only from **that row's own call**, not from
the cumulative effect of the ones before it.

#### The Bounding Measurement

Who frees what was allocated on partial failure must be written **separately** in every
function. `record_create` frees its own allocation; so does `triple_alloc` — but this is not a
language rule, it is a contract **those functions chose for themselves**. Another function
could return `NULL` without freeing what it allocated on partial failure, and the compiler
would not stop it; the contract is written only in the comment in the header file, and the
caller **has to read it**.

This also draws the boundary of what the lesson measures: we showed that `record_create` and
`triple_alloc` clean up their own failure paths correctly, but that these two being written
correctly is not proof that some other function will follow the same discipline. The same
question has to be asked again for every new function: what was allocated on partial failure,
who will free it, and where is that contract written? The measurement stops here; this is a
lesson that runs **one instance** of the contract, not a tool that checks whether the contract
exists.

This boundary applies the same way to `errno`: the standard guarantees that a function will
set `errno` on failure, but it does not guarantee **which functions** may overwrite it
without reading it first. The only way to observe the difference is to reset `errno`
immediately before every call and read it immediately after — if another library function
runs between the two calls, that function may change `errno` even if it succeeds. Both
patterns measured in this lesson (`goto cleanup` and resetting `errno`) rest on the same
principle: the language never performs any cleanup or any reset **on its own**; the party
that does it is always the line of code itself.

#### Summary

- A function may free the block it allocated on partial failure itself (`record_create`), or
  return without freeing it; which one happens is the function's **own contract**.
- Single-exit-point cleanup (`goto cleanup`) is the standard pattern for rolling back
  multiple resources in sequence, and it brought the counter difference to **0** at both
  failure points.
- `errno` is only read for **whether it is nonzero**; neither its numeric value nor a
  message text was ever printed in any observation.
- The memory this lesson measures is in the **callee** class: the module itself took on
  cleanup on the error path, leaving nothing for the caller.
- All three patterns share one point: the language provides none of them **on its own** —
  whoever writes the `goto cleanup` label, guarantees that `count_free` no-ops on `NULL`, and
  resets `errno` before every call, it is always the programmer's own lines.

#### Next Step

While cleaning up on the error path, `record_create` also rejected something: a given name
not fitting the limit. That limit — the `limit` parameter — does not come from the source, it
is given **by the caller**. The next lesson puts that parameter at the center: how giving the
length to the caller prevents a buffer overflow.

### Bounds Checking

Source: https://academia.sh/en/courses/c-memory/bounds-checking

record_create's limit parameter takes its length not from the source but from the caller; the lesson runs how this parameter prevents a buffer overflow, snprintf's truncation notification, and whose responsibility the length is.

The previous lesson measured `record_create`'s cleanup on the error path; that error path
arose from a single condition: the given name's length exceeded the `limit` parameter. This
lesson puts that parameter itself at the center. `limit` is not a constant of the language or
of the `owner.c` unit — it is a value the **caller** gives on every call, and it is exactly
that value that prevents a buffer overflow.

The previous two lessons measured the "unowned" and "callee" classes; this lesson moves to
the third class: **caller**. The difference comes down to one question — who prevents a
buffer overflow? In the previous lesson the answer was "the called function itself":
`record_create` bounded the block it allocated itself. In this lesson, neither of the two
functions measured (`record_create`'s `limit`, `bounded_copy`'s `dest_size`) **sets its own**
limit — the caller sets the limit and the function only **complies** with it. Whether the
limit is correct is the caller's responsibility, not the function's.

#### The Caller Gives the Length

Looking at the signature `record_create(const char *name, size_t limit)`, where `limit` comes
from is not visible; the signature only takes a `size_t`. But it is written in the comment in
the contract's header file: the function checks `name`'s length against `limit` and returns
`NULL` if it does not fit. This check is not part of the language — `strlen` knows no limit,
`memcpy` knows no limit; the only thing that knows the limit is the comparison inside
`record_create`'s own body.

What needs attention at this point is **where** `limit` comes from. `size_t limit` appearing
in the signature does not mean the value is constant or safe — the caller may derive it from
a constant, from user input, or from another computation. `record_create` does not check
whether the `limit` given to it is **correct**, it only checks whether `name` fits that
`limit`. If the limit itself is wrong — for example larger than the real destination's
capacity — the function still works correctly, but what the check protects is no longer the
real destination, it is the number the caller wrongly reported. This distinction is taken up
again in the "Bounding Measurement" section at the end of the lesson.

```c
// counter.h — a wrapper that counts every allocation and every free
#ifndef COUNTER_H
#define COUNTER_H

#include <stddef.h>

void *count_alloc(size_t size);
void *count_grow(void *old, size_t size);
void count_free(void *block);
int allocated_count(void);
int freed_count(void);

#endif
```

```c
// counter.c — the counters live only in this unit
#include <stdlib.h>
#include "counter.h"

static int allocated = 0;
static int freed = 0;

void *count_alloc(size_t size) {
    void *block = malloc(size);
    if (block != NULL) { allocated++; }
    return block;
}

void *count_grow(void *old, size_t size) {
    int new_block = (old == NULL);
    void *grown = realloc(old, size);
    if (grown != NULL && new_block) { allocated++; }
    return grown;
}

void count_free(void *block) {
    if (block != NULL) { freed++; }
    free(block);
}

int allocated_count(void) { return allocated; }
int freed_count(void) { return freed; }
```

```c
// owner.h — the record's inside is hidden, the ownership contract is in the comments
#ifndef OWNER_H
#define OWNER_H

#include <stddef.h>

typedef struct Record Record;

/* The caller owns it and frees it with record_destroy. If the name does not
   fit the limit, returns NULL; the function frees what it allocated so far. */
Record *record_create(const char *name, size_t limit);

/* Frees the record together with the name buffer inside it. */
void record_destroy(Record *r);

/* The record owns it: the returned pointer is valid until the record is
   destroyed, and the caller does not free it. */
const char *record_name(const Record *r);

/* The caller owns it: the returned copy is freed with count_free. */
char *record_name_copy(const Record *r);

#endif
```

```c
// owner.c — the unit where the contract is carried out
#include <string.h>
#include "owner.h"
#include "counter.h"

struct Record {
    char *name;
    size_t length;
};

Record *record_create(const char *name, size_t limit) {
    Record *r = count_alloc(sizeof *r);
    if (r == NULL) { return NULL; }

    r->length = strlen(name);
    if (r->length >= limit) {
        count_free(r);
        return NULL;
    }

    r->name = count_alloc(r->length + 1);
    if (r->name == NULL) {
        count_free(r);
        return NULL;
    }
    memcpy(r->name, name, r->length + 1);
    return r;
}

void record_destroy(Record *r) {
    if (r == NULL) { return; }
    count_free(r->name);
    count_free(r);
}

const char *record_name(const Record *r) { return r->name; }

char *record_name_copy(const Record *r) {
    char *copy = count_alloc(r->length + 1);
    if (copy != NULL) { memcpy(copy, r->name, r->length + 1); }
    return copy;
}
```

**RO7.** `record_create` can give two different results when called with the same `name` but
two different `limit` values — what decides the result is not `name`, it is the `limit` the
caller gives. The measurement below shows exactly this: the same name, with one narrow and
one wide limit.

This observation looks at `record_create` from a deliberately different angle than the
previous lesson's measurement of it. The previous lesson held `limit` **constant** and
triggered the error path by changing `name`'s length; this lesson does the opposite — it
holds `name` **constant** and changes `limit`. Both trigger the same comparison (`r->length
>= limit`), but showing which side changes clarifies which parameter the contract belongs to:
the comparison looks at both variables, but because the question this lesson asks is
specifically "who gives the limit," the side that must stay constant is `name`, and the side
that must change is `limit`.

#### Truncating Copy into a Fixed-Size Buffer

`record_create` allocates and copies the source; but sometimes the destination is a **fixed-
size** local buffer and the source's length is not known in advance. In that case using
`strcpy` leaves the door open to a buffer overflow: `strcpy` never knows the destination's
size, it copies the source as-is. `snprintf`, on the other hand, takes the destination's size
as a parameter and never exceeds it; when it truncates, it **reports** this through its return
value — the return value is the total length that would have been written had there been no
truncation.

The difference between these two patterns is clearer when compared with `record_create` from
the previous section. `record_create` returns `NULL` without writing anything when the limit
is exceeded — no partial write is involved. `bounded_copy` behaves differently: it writes
**as much as fits** into the destination and separately reports the real length. Both prevent
a buffer overflow, but one says "either it fits fully or I write nothing," while the other
says "I write as much as fits, but I also tell you how many characters did not fit." Which
pattern is right depends on what the destination is used for: for data whose integrity must
not be broken, such as a record name, the first is preferred; for text where truncation is
acceptable, such as a log line, the second is.

```c
// truncate.h — declaration of a truncating copy into a bounded destination
#ifndef TRUNCATE_H
#define TRUNCATE_H

#include <stddef.h>

/* The destination's size is dest_size. The returned value is the total
   length that would have been written had there been no truncation; if
   this value is larger than dest_size, truncation occurred. */
size_t bounded_copy(char *dest, size_t dest_size, const char *source);

#endif
```

```c
// truncate.c — copying without exceeding the limit, using snprintf
#include <stdio.h>
#include "truncate.h"

size_t bounded_copy(char *dest, size_t dest_size, const char *source) {
    int needed = snprintf(dest, dest_size, "%s", source);
    if (needed < 0) {
        return 0;
    }
    return (size_t)needed;
}
```

**RO8.** Comparing `bounded_copy`'s returned value with `dest_size - 1` tells whether
truncation occurred: the return value is the length that does **not** fit the destination,
not the length actually written to the destination. These two numbers may be the same (no
truncation) or different (truncation) — the lesson shows both in the measurement below.
Comparing against `dest_size - 1` follows from the fact that `snprintf` always adds a
terminating zero byte to the destination: a `dest_size`-byte space fits at most `dest_size -
1` characters plus one terminator.

#### One Measurement with Two Limits

The single `main` below measures the same two sources with two different limits: a narrow and
a wide `limit` for `record_create`, a small and a sufficient `dest_size` for `bounded_copy`.

```c
// measurement.c — measuring the caller-given limit with two different values
#include <stdio.h>
#include "owner.h"
#include "counter.h"
#include "truncate.h"

static void observe(const char *name, const char *result) {
    printf("%-28s%s\n", name, result);
}

int main(void) {
    char buffer[64];

    printf("%-28s%s\n", "observation", "result");

    /* --- record_create: same name, two different limits --- */
    Record *narrow = record_create("toughness", 6);
    snprintf(buffer, sizeof buffer, "record %s", narrow == NULL ? "rejected" : "accepted");
    observe("narrow limit (6)", buffer);

    Record *wide = record_create("toughness", 32);
    snprintf(buffer, sizeof buffer, "record %s", wide == NULL ? "rejected" : "accepted");
    observe("wide limit (32)", buffer);
    record_destroy(wide);

    /* --- bounded_copy: same source, two different destination sizes --- */
    char small[8];
    size_t needed_small = bounded_copy(small, sizeof small, "toughness class");
    snprintf(buffer, sizeof buffer, "needed %zu, fits %zu, %s",
             needed_small, sizeof small - 1,
             needed_small >= sizeof small ? "truncated" : "not truncated");
    observe("small dest (8)", buffer);

    char sufficient[32];
    size_t needed_sufficient = bounded_copy(sufficient, sizeof sufficient, "toughness class");
    snprintf(buffer, sizeof buffer, "needed %zu, fits %zu, %s",
             needed_sufficient, sizeof sufficient - 1,
             needed_sufficient >= sizeof sufficient ? "truncated" : "not truncated");
    observe("sufficient dest (32)", buffer);

    return 0;
}
```

```text
observation                 result
narrow limit (6)            record rejected
wide limit (32)             record accepted
small dest (8)              needed 15, fits 7, truncated
sufficient dest (32)        needed 15, fits 31, not truncated
```

**RO9.** `"toughness"` is nine characters; the narrow limit (**6**) rejects it, the wide limit
(**32**) accepts the same name — the difference lies not in the source, but in the number the
caller gives. In `bounded_copy` too, the same source (`"toughness class"`, fifteen characters)
is measured with two different destination sizes: the eight-byte destination is truncated
because it fits seven characters, the thirty-two-byte destination is not truncated because it
comfortably fits fifteen characters — the `needed` value stays the **same** across both calls,
only the destination's capacity changes.

Two of the four observations come through `record_create`, two through `bounded_copy`, but
all four share the same structure: **same source, two different limits**. This repetition is
deliberate — the lesson does not have a single piece of evidence, it has two independent
pieces of evidence in two independent functions showing the same principle. `record_create`
rejects entirely when the limit is exceeded; `bounded_copy` partially accepts and reports the
state when the limit is exceeded. Two different responses show how the same source (the
caller-given limit) can determine two different outcomes. The `before`/`diff` arrangement
inside `main` is not needed here, because no observation depends on the previous one's
counter state — both are measured independently with their own input and their own limit;
of the `narrow` and `wide` pointers, only `wide` is freed, because `narrow` is already `NULL`,
and `record_destroy(NULL)` safely does nothing.

#### The Bounding Measurement

Whether the `limit` and `dest_size` parameters are called with the **correct** value is not
something the compiler checks. Giving an excessively large limit disconnected from the real
size, like `record_create(name, 1000000)`, also compiles; in that case the function still
applies its own internal check correctly, but the check becomes **meaningless**, because the
caller has misreported the real destination's size. Bounds checking does not save anything if
the bound itself is wrong.

The same bound applies to `bounded_copy`'s `dest_size` parameter as well. The `sizeof small`
expression works correctly here because `small` is a real array variable and the compiler
knows its size; but if `sizeof` were applied to a pointer parameter — for example if `small`
were taken as a function parameter written `char *small` — `sizeof small` would give the
pointer's own size, not the array's size, and this would silently produce a wrong
`dest_size`. This lesson does not fall into that trap because `sizeof small` and `sizeof
sufficient` are called directly over the array definitions inside `main`, but the distinction
can be missed when carrying the pattern into another function; `dest_size` must always be
computed **in the scope where the array is defined**, not after it has decayed into a
pointer.

Even once the limit's correctness has been established, the limit's **persistence** is a
separate question. The `limit` given to `record_create` is valid only for that single call;
when the same `Record` is later passed to another function, the original `limit` value is not
carried along with it — only the pointer `record_name` returns and the copy `record_name_copy`
produces are carried along. This shows that the caller's responsibility does not end with a
single call: the caller that first gives the limit must also document under what assumption
(for example "this name will be at most thirty-one characters") it was given, because later
code can learn that assumption only by reading the documentation, not by reading it from the
source.

#### Summary

- `record_create`'s `limit` parameter comes from the **caller**, not the source; the same
  name gave two different results with two different limits.
- `snprintf` provided a copy that takes the destination's size as a parameter, never exceeds
  it, and reports truncation through its return value; `strcpy` never provides this report at
  all.
- When the same source was measured with two different destination sizes, the `needed` length
  stayed constant, only whether it was truncated changed — the limit's correctness depends on
  the destination's real capacity.
- The memory this lesson measures is in the **caller** class: the party that knows and
  reports the destination's size is the call itself that gives the `limit` or `dest_size`
  parameter in the code.
- These three lessons measured three separate classes in the course's "who owns it" axis in
  sequence: the previous two lessons measured the unowned and callee classes through moments
  the contract broke and the function failed, this lesson measured the caller class by showing
  that the limit is written in the caller, not the source. What the three lessons share is
  that in none of them is the protecting party the language — protection each time comes from
  a line that a separate party added itself.

#### Next Step

These three lessons measured the moments the contract broke, the function failed, and the
caller gave a limit — all three ran on the same platform, the same compiler. This shared
assumption has never been questioned so far: every measurement produced the same result under
the same `cc`, the same flags, the same operating system. But even defined behavior itself is
not identical everywhere — part of it is defined by the standard, while which **value** it
takes can remain implementation-defined; even the width of `size_t` itself, the type of the
length `bounded_copy` returns as measured in this lesson, can vary from platform to platform.
The next lesson takes up how much this contract varies **across platforms**: which behavior
stays defined, which is implementation-defined, and how code keeps this portable. The three
examples in this lesson are therefore evidence of only one platform — even though the result
itself stays unchanged, which assumptions carry that result has not yet been written down
explicitly. That writing-down work begins without changing a single line of the lesson's
code, only by changing the build environment.

### Portability

Source: https://academia.sh/en/courses/c-memory/portability

Three values the shared core deliberately never printed — addresses, type sizes other than char, malloc(0)'s return — are target-dependent; this lesson isolates target-dependent behavior by printing a comparison the standard guarantees instead of a value, and gathers the course's fifteen lessons into a closing table.

The previous lesson measured that the `limit` parameter is written in the caller, not the
source. This lesson closes the course itself and leaves behind a single question: what the
measurement core never **printed** from the start. `ownership.c` printed thirteen
observations, but it never printed three things — addresses, type sizes other than
`sizeof(char)`, and whether `malloc(0)` returns null. All three were left out for the same
reason: all three are **target-dependent**, meaning the same source text can produce two
different numbers on two different targets.

This lesson does not try to print those three values. Instead it prints a comparison the
standard **guarantees**. The difference is this: printing `sizeof(int)` gives a number, and
that number changes by target; asking the question `sizeof(char) == 1` also gives a result,
but this result is the **same** on every target, because the standard itself guarantees it.
What is measured is not two approaches to the same question — one exposes a target-dependent
value, the other exposes the target-independent logical result the standard leaves fixed.
This lesson's ownership class is the **language**: the party giving the guarantee is not a
caller, a callee, or a block left unowned, it is the language's standard itself.

#### Three Comparisons, One Isolation Technique

The measurement core's counter unit is also used in this lesson; its excerpt is below,
unchanged.

```c
// counter.h — a wrapper that counts every allocation and every free
#ifndef COUNTER_H
#define COUNTER_H

#include <stddef.h>

void *count_alloc(size_t size);
void *count_grow(void *old, size_t size);
void count_free(void *block);
int allocated_count(void);
int freed_count(void);

#endif
```

```c
// counter.c — the counters live only in this unit
#include <stdlib.h>
#include "counter.h"

static int allocated = 0;
static int freed = 0;

void *count_alloc(size_t size) {
    void *block = malloc(size);
    if (block != NULL) {
        allocated++;
    }
    return block;
}

void *count_grow(void *old, size_t size) {
    int new_block = (old == NULL);
    void *grown = realloc(old, size);
    if (grown != NULL && new_block) {
        allocated++;
    }
    return grown;
}

void count_free(void *block) {
    if (block != NULL) {
        freed++;
    }
    free(block);
}

int allocated_count(void) {
    return allocated;
}

int freed_count(void) {
    return freed;
}
```

**RO10.** `counter.c` keeps the same behavior in this lesson too: `count_alloc` increments its
counter only if the return is not `NULL`, `count_free` increments its counter only if the
argument is not `NULL` and still calls `free` regardless. These two lines are set up in
advance for this lesson's last observation — even though what `malloc` returns when `size` is
zero is target-dependent, the behavior of these two lines in `counter.c` **does not change**.

The new file measures the three comparisons and a zero-size allocation together. No line
prints a size, a limit value, or an address; every line only prints a comparison's result.

```c
// portability.c — prints the standard's guarantee instead of a value
#include <stdint.h>
#include <limits.h>
#include <stdio.h>
#include "counter.h"

static void observe(const char *name, const char *result) {
    printf("%-34s%s\n", name, result);
}

int main(void) {
    printf("%-34s%s\n", "observation", "result");

    observe("fixed-width integer", sizeof(int32_t) * CHAR_BIT == 32 ? "guarantee holds" : "guarantee does not hold");
    observe("char size", sizeof(char) == 1 ? "guarantee holds" : "guarantee does not hold");
    observe("int lower bound", INT_MAX >= 32767 ? "guarantee holds" : "guarantee does not hold");

    int first = 7;
    int second = 7;
    int *same = &first;
    int *again = &first;
    int *different = &second;
    observe("two addresses, one variable", same == again ? "equal" : "not equal");
    observe("two addresses, two variables", same == different ? "equal" : "not equal");

    int before = allocated_count() - freed_count();
    void *zero_size = count_alloc(0);
    count_free(zero_size);
    int after = allocated_count() - freed_count();
    observe("zero-size allocation contract", after - before == 0 ? "contract held" : "contract broken");

    return 0;
}
```

```
observation                       result
fixed-width integer               guarantee holds
char size                         guarantee holds
int lower bound                   guarantee holds
two addresses, one variable       equal
two addresses, two variables      not equal
zero-size allocation contract     contract held
```

**RO11.** The first three lines follow the same pattern: instead of directly printing a
type's size or a limit value, each compares it against a lower bound the standard already
guarantees. When `int32_t` exists it is exactly thirty-two bits wide — this comes from the
definition itself, not from the target. This is also why the comparison is built on bit count
rather than byte count: the number of bits in a byte (`CHAR_BIT`) is guaranteed by the
standard only to be at least eight, so `sizeof(int32_t)` can vary from target to target, while
`sizeof(int32_t) * CHAR_BIT` does not. `sizeof(char)` is always one — this too comes from the
definition; this is exactly why no size other than `sizeof(char)` has been printed anywhere
in the course. `INT_MAX` is guaranteed by the standard to be at least `32767`; its real value
may be larger depending on the target, but whether this lower bound **holds** gives the same
result on every target.

**RO12.** The fourth and fifth lines measure pointer equality without printing addresses.
`same` and `again` hold the address of the same variable; their equality comes from a
language rule — the same expression (`&first`) gives the same result every time it is
evaluated. `same` and `different`, on the other hand, hold the addresses of two separate
automatic objects alive at the same time; the standard guarantees that these two objects will
not overlap, so the inequality too is not an implementation coincidence, it is a language
guarantee. Both lines measure these two guarantees without ever showing the address
**itself**.

**RO13.** The last line takes up the shared core's third unprinted value: whether `malloc(0)`
returns null is target-dependent, and this lesson does **not answer** that question. Instead
it measures whether `counter.c`'s two lines are affected by that ambiguity. If `count_alloc(0)`
returns `NULL`, the counter never increments and `count_free(NULL)` never increments either —
the difference stays zero. If `count_alloc(0)` returns a valid pointer, the counter increments
by one and the counter increments by one again as `count_free` frees it — the difference
still stays zero. Which branch `malloc(0)` took is never visible; the only thing visible is
that the contract stays the **same on both branches**.

#### The Fixed-Width Types' Own Limit

The isolation `<stdint.h>` offers is not unlimited by itself. The standard requires
full-width types like `int32_t` to be defined only **if** the target platform has such a
type; in its absence these types are not defined at all, and a source file using them does
not compile. The standard gives the types it requires to exist on every target separate
names — `int_least32_t` guarantees being at least thirty-two bits wide, `int32_t` guarantees
being exactly thirty-two bits wide, but only when it exists.

**RO14.** That this lesson uses `int32_t` directly instead of testing for its existence is an
assumption made by the lesson itself: this type **exists** in the environment where the
lesson is compiled. The isolation technique itself thus also rests on an assumption — not
about the width, but about the type's **existence**. Where the exact width is not required, a
portable source uses `int_least32_t` or `int_fast32_t`; this lesson chose `int32_t` only as an
example.

#### The Bounding Measurement

Printing a guarantee instead of a value does not **eliminate** what is target-dependent — it
only moves it outside the observation. The expression `sizeof(int32_t) * CHAR_BIT == 32` gives
the same result on every target, but only when `int32_t` **exists**; the standard itself does
not require this type to exist. Likewise `INT_MAX >= 32767` holds on every target, but the
real `INT_MAX` value can vary from target to target — the lesson preserves portability by
never printing this value, but leaves the actual question it wanted to test (exactly what
`INT_MAX` is) unanswered. A comparison is more portable than a value, but it carries less
information; this trade-off between portability and information marks **where** the
language's guarantee ends. None of the measurement core's four classes eliminates this
trade-off — including the language class, because the language that gives the guarantee also
draws the guarantee's own boundary.

#### Summary

- The memory class this lesson measures is the **language**: the party giving the guarantee
  is not a caller, a callee, or a block left unowned, it is the language's standard itself.
- Three values the shared core deliberately never printed — addresses, type sizes other than
  `sizeof(char)`, `malloc(0)`'s return — are all target-dependent, and none of them were
  printed in this lesson either.
- When a comparison the standard guarantees is printed instead of a value, the result stays
  the same on every target; `sizeof(int32_t) * CHAR_BIT == 32`, `sizeof(char) == 1`, and
  `INT_MAX >= 32767` all followed this pattern.
- The addresses of two automatic objects alive at the same time do not overlap; this
  inequality was measured only with the `==` comparison, never by printing the address
  itself.
- Whether `malloc(0)` returns null stays target-dependent, but `counter.c`'s
  allocate-free contract was unaffected by that ambiguity: the difference stayed zero on
  both branches.
- Printing a guarantee does not eliminate what is target-dependent, it only moves it outside
  the observation — `int32_t` itself is a type that may not exist.

#### Course Wrap-Up

Throughout the course, a single question was asked: who owns this memory. Fourteen lessons
answered that question with four classes — language, caller, callee, unowned — and this
lesson showed what the same question says when asked of the language itself: where the
standard guarantees something, the language owns it; where the guarantee ends, the
observation stays target-dependent and is not printed.

| Lesson | Memory measured | Owner | Bounding measurement |
|---|---|---|---|
| Pointer Basics | Local variable, static storage, string literal, local array | language | A pointer's type does not say who will free it |
| Pointer Arithmetic | In-array advance and element difference | language | Pointing one past the end is valid, dereferencing it is undefined |
| Arrays and Pointers | Dynamically allocated array | caller | Decay works at only one level, it does not carry the element count |
| Null and Dangling Pointers | Pointer pointing at a freed block | unowned | Assigning NULL only makes that one pointer checkable, it does not affect another pointer pointing at the same block |
| `void` and Opaque Pointers | Opaque record (`Record`) | callee | Opacity hides the struct's inside, not its ownership: the obligation to free is written only in the comment |
| Function Pointers | Context carried into a callback | caller | A signature guarantees no behavior, it only checks type compatibility |
| Memory Allocation Functions | Block allocated with malloc/calloc/realloc | caller | malloc(0) and realloc(block, 0)'s return is implementation-defined, never printed |
| Deallocation Discipline | Lent name and returned copy | callee | Ownership is read not from the signature but from the comment in the header file |
| Memory Leaks | Two unowned blocks | unowned | A leak produces no crash or warning, it only shows up as a counter difference |
| Memory Debugging Tools | Balanced and unbalanced allocate-free pair | unowned | A tool only counts the unmatched call, it does not read the contract violation |
| Memory Layout | Variable in one of four memory regions | language | Region boundaries and alignment are not printed; outside the heap, ownership comes from the contract, not the region |
| Undefined Behavior | Defined counterpart of four undefined operations | unowned | Looking like it works does not show that it is defined |
| Error Handling Patterns | Block cleaned up on the error path | callee | Who cleans up on partial failure must be written separately in every function |
| Bounds Checking | Copy bounded with snprintf | caller | The function only checks the limit it is told, it cannot catch a wrongly reported limit |
| Portability | Fixed-width type, sizeof(char), INT_MAX, zero-size allocation | language | The isolation technique itself rests on a guarantee too: int32_t's existence is not required by the standard |

The lessons are distributed across classes as **4 language · 4 caller · 3 callee · 4
unowned** — this is the lessons' distribution and is not to be confused with the shared
core's thirteen observations' 4/4/3/2.

The four classes never changed across fifteen lessons, but each lesson showed the class
through a different face: sometimes the owning party was a function, sometimes a contract,
sometimes nobody at all, and sometimes, as seen in this last lesson, the language itself. A
pointer's type never once told its owner; the owner was decided every time either by the
source itself (an `&` expression, an allocation call) or by the contract written in the
header file.

The next course turns to C and systems programming, to operating-system interfaces. This
course's ownership question keeps holding there too — who owns a file descriptor, a process,
or a signal is shaped in the same form as the question asked for memory — but that course
will establish its own resources' classes with its own measurement.

