Implementing free

medium · memory, deallocation, pointers

Free: Marking Blocks for Reuse

Now that we have malloc, we need free(). Without it, memory can only grow - never be reused.

What free() Does

free() does NOT return memory to the OS. It marks the block as available for reuse:

Before free(p):
┌──────────────┬───────────────────┐
│ size=100     │                   │
│ free=0  ────────► ALLOCATED      │
│ next=...     │                   │
└──────────────┴───────────────────┘

After free(p):
┌──────────────┬───────────────────┐
│ size=100     │                   │
│ free=1  ────────► FREE (reusable)│
│ next=...     │                   │
└──────────────┴───────────────────┘

The Critical Pointer Arithmetic

When the user calls free(ptr), they give us the pointer to their DATA. We need to find the HEADER:

void my_free(void *ptr) {
    // ptr points to user data
    // Header is immediately BEFORE the data
    block_header_t *block = (block_header_t*)ptr - 1;
    block->free = 1;
}

Your Task

// Get the block header from a user pointer
block_header_t *get_block(void *ptr);

// Free the memory pointed to by ptr
// - Find the block header
// - Mark it as free
// - Handle NULL gracefully (do nothing)
void my_free(void *ptr);

Safety Considerations

Real allocators add checks:

  1. NULL check: free(NULL) should be a no-op (do nothing)
  2. Double-free detection: Freeing already-freed memory is a bug
  3. Invalid pointer detection: Only free pointers from malloc

For now, just implement the basics with the NULL check.

Why Not Return Memory to OS?

Consider this sequence:

void *a = malloc(100);  // Block A
void *b = malloc(100);  // Block B
void *c = malloc(100);  // Block C
free(b);                // Free middle block

The heap looks like: [A used][B free][C used]

We can't shrink the heap because C is still in use! We can only shrink from the TOP. So free() just marks blocks for reuse.

Run tests to see results
No issues detected