Block Coalescing
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