Block Coalescing

hard · memory, fragmentation, optimization

Block Coalescing: Reducing External Fragmentation

When you free multiple adjacent blocks, they remain separate unless you coalesce (merge) them. This creates external fragmentation: many small free blocks that can't satisfy large allocations.

The Problem

Without coalescing:

void *p1 = my_malloc(100);  // Block A
void *p2 = my_malloc(100);  // Block B
void *p3 = my_malloc(100);  // Block C

my_free(p1);  // A is free
my_free(p2);  // B is free
my_free(p3);  // C is free

void *big = my_malloc(250);  // FAILS! 3 separate 100-byte blocks
                              // can't satisfy 250-byte request

With coalescing:

void *p1 = my_malloc(100);
void *p2 = my_malloc(100);
void *p3 = my_malloc(100);

my_free(p1);  // A is free
my_free(p2);  // B merges with A → 200+ bytes
my_free(p3);  // C merges with AB → 300+ bytes

void *big = my_malloc(250);  // SUCCESS! One large block

Memory Layout

Before coalescing (all blocks free):
┌────────┬──────────┐┌────────┬──────────┐┌────────┬──────────┐
│ hdr A  │ data A   ││ hdr B  │ data B   ││ hdr C  │ data C   │
│ free=1 │ 100B     ││ free=1 │ 100B     ││ free=1 │ 100B     │
└────────┴──────────┘└────────┴──────────┘└────────┴──────────┘
    ↓ coalesce during free(B) and free(C)

After coalescing:
┌────────┬─────────────────────────────────────────────────────┐
│ hdr A  │              data: ~300B + 2*header_size            │
│ free=1 │                    (merged A+B+C)                   │
└────────┴─────────────────────────────────────────────────────┘

Your Task

Implement block coalescing in your free:

#define BLOCK_MAGIC 0xDEADBEEF
#define MIN_BLOCK_SIZE (sizeof(block_header_t) + 16)

// Coalesce with the next block if it's free
// Returns 1 if coalescing occurred, 0 otherwise
int coalesce_next(block_header_t *block);

// Coalesce with the previous block if it's free
// Returns pointer to the merged block, or 'block' if no merge
block_header_t *coalesce_prev(block_header_t *block);

// Full coalescing (next then prev)
block_header_t *coalesce(block_header_t *block);

// Free with coalescing
void my_free(void *ptr);

Coalescing with Next Block

Finding the next block is straightforward:

block_header_t *next = (block_header_t*)(
    (char*)block + sizeof(block_header_t) + block->size
);

// Check bounds and if free
void *heap_end = sbrk(0);
if ((void*)next < heap_end && next->free && next->magic == BLOCK_MAGIC) {
    block->size += sizeof(block_header_t) + next->size;
    block->next = next->next;  // Skip 'next' in linked list
}

The Hard Problem: Previous Block

With a singly-linked list, you can't go backward easily:

// Option 1: Scan from head (O(n) but simple)
block_header_t *candidate = free_list_head;
while (candidate != NULL) {
    block_header_t *cand_next = next_in_memory(candidate);
    if (cand_next == block && candidate->free) {
        // candidate is immediately before block - merge!
        candidate->size += sizeof(block_header_t) + block->size;
        candidate->next = block->next;
        return candidate;
    }
    candidate = candidate->next;
}

Critical Test: Adjacent Frees Must Merge

void *p1 = my_malloc(100);
void *p2 = my_malloc(100);
my_free(p1);
my_free(p2);

// Count blocks - should be 1 (merged), not 2
int count = 0;
block_header_t *curr = free_list_head;
while (curr != NULL) { count++; curr = curr->next; }
assert(count == 1);

// Large allocation should now succeed
void *big = my_malloc(180);
assert(big != NULL);  // Uses the merged block!

The Magic Field

Always validate magic before trusting block data:

if ((void*)next < heap_end && next->magic == BLOCK_MAGIC && next->free) {
    // Safe to coalesce
}

This catches corruption before it causes crashes.

Run tests to see results
No issues detected