---
title: 'Memory Allocation Functions'
source: 'https://academia.sh/en/courses/c-memory/memory-allocation-functions'
course: 'Memory and Pointers in C'
language: en
updated: '2026-09-26T03:02:42+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Measures with the counter difference that the owner of the block allocated by malloc, calloc, and realloc is the caller in every case'
    - 'Shows that calloc zeroes the block it allocates by reading the values directly'
    - 'Explains that a resize does not change ownership of the old block and that on failure the old block remains valid'
---

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