Block Splitting

hard · memory, fragmentation, optimization

Block Splitting: Reducing Internal Fragmentation

When you allocate 50 bytes but find a 1000-byte free block, the naive approach wastes 950 bytes. Block splitting divides large blocks to reduce this waste.

The Problem

Without splitting:

void *p1 = my_malloc(1000);  // Get a 1000-byte block
my_free(p1);                  // Free it
void *p2 = my_malloc(50);     // Reuse the 1000-byte block!
                              // 950 bytes wasted (internal fragmentation)

With splitting:

void *p1 = my_malloc(1000);  // Get a 1000-byte block
my_free(p1);                  // Free it
void *p2 = my_malloc(50);     // Split: use 50, remainder is new free block
void *p3 = my_malloc(100);    // Use the remainder!

Memory Layout

Before split (1000-byte free block, requesting 50):
┌────────────────┬─────────────────────────────────────────┐
│ header (32B)   │        data region (1000B)              │
│ size=1000      │                                         │
│ free=1         │                                         │
└────────────────┴─────────────────────────────────────────┘

After split:
┌────────────────┬──────────┬────────────────┬─────────────┐
│ header (32B)   │ data(56B)│ new header(32B)│ data(~900B) │
│ size=56        │ (USED)   │ size=~900      │  (FREE)     │
│ free=0         │          │ free=1         │             │
└────────────────┴──────────┴────────────────┴─────────────┘
                             ↑
                             New block can satisfy future allocations!

Your Task

Implement block splitting in your malloc:

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

// Check if block can be split for given size
int can_split(block_header_t *block, size_t size);

// Split block, returns new free block (or NULL if not split)
block_header_t *split_block(block_header_t *block, size_t size);

// malloc with splitting support
void *my_malloc(size_t size);

// free with magic validation
void my_free(void *ptr);

Critical Test: Remainder Must Be Usable

void *p1 = my_malloc(1000);
my_free(p1);

void *p2 = my_malloc(50);   // Should split the 1000-byte block
void *p3 = my_malloc(100);  // Should use the remainder!

// p2 and p3 should be DIFFERENT and VALID
// If p3 == NULL or p3 == p2, splitting failed

Alignment Matters

Always align sizes to 8 bytes before splitting:

size_t aligned = (size + 7) & ~7;

If you split without aligning, the new block's header might be misaligned, causing crashes on some architectures.

The Magic Field

Add a magic number to detect corruption:

typedef struct block_header {
    size_t size;
    int free;
    unsigned int magic;  // Set to BLOCK_MAGIC
    struct block_header *next;
} block_header_t;

Validate magic before trusting any block data.

Run tests to see results
No issues detected