Custom Memory Allocator implementation demonstrating advanced memory management techniques in C.
A high-performance custom heap memory allocator that replaces the standard malloc, free, calloc, and realloc functions. This implementation demonstrates advanced memory management techniques including segregated free lists, block coalescing, and adaptive memory acquisition strategies.
- Complete malloc/free/calloc/realloc replacement - Drop-in compatible API
- Dual memory acquisition strategy - Uses
brk()for small allocations andmmap()for large ones - Segregated free lists - 10 size classes for efficient allocation and reduced fragmentation
- Block splitting and coalescing - Minimizes external and internal fragmentation
- Thread-unsafe and thread-safe versions - Choose performance or safety
- Comprehensive benchmarks - Performance comparison with system malloc
- Valgrind compatible - Proper memory tracking and leak detection
- Detailed statistics - Track allocations, frees, splits, and coalesces
The allocator uses a hybrid approach for memory acquisition:
-
Small allocations (< 128KB): Uses
brk()system call to expand the heap- More efficient for frequent small allocations
- Better locality of reference
- Heap grows in 64KB increments
-
Large allocations (≥ 128KB): Uses
mmap()for direct memory mapping- Avoids heap fragmentation for large blocks
- Can be unmapped individually
- Reduces memory waste
The allocator maintains 10 size classes to reduce search time and fragmentation:
| Class | Size Range | Use Case |
|---|---|---|
| 0 | ≤ 32 bytes | Small objects, metadata |
| 1 | ≤ 64 bytes | Small structures |
| 2 | ≤ 128 bytes | String buffers |
| 3 | ≤ 256 bytes | Medium structures |
| 4 | ≤ 512 bytes | Small arrays |
| 5 | ≤ 1024 bytes | Medium buffers |
| 6 | ≤ 2048 bytes | Large structures |
| 7 | ≤ 4096 bytes | Page-sized allocations |
| 8 | ≤ 8192 bytes | Multi-page allocations |
| 9 | > 8192 bytes | Large allocations |
Each allocated block has a header containing:
- Size of the block (including header)
- Pointers to next and previous blocks in free list
- Free/allocated status flag
- mmap allocation flag
+------------------+
| Block Header |
| - size |
| - next pointer |
| - prev pointer |
| - is_free flag |
| - is_mmap flag |
+------------------+
| User Data |
| ... |
+------------------+
When a block is freed, the allocator checks adjacent blocks and merges them if they are also free. This reduces external fragmentation and makes larger contiguous blocks available for future allocations.
When allocating from a free block that is significantly larger than needed, the allocator splits it into two blocks:
- One block for the requested allocation
- One free block returned to the appropriate free list
Minimum split size ensures we don't create unusably small fragments.
void* mem_malloc(size_t size);Allocates size bytes and returns a pointer to the allocated memory.
void mem_free(void* ptr);Frees the memory space pointed to by ptr.
void* mem_calloc(size_t nmemb, size_t size);Allocates memory for an array of nmemb elements of size bytes each and initializes to zero.
void* mem_realloc(void* ptr, size_t size);Changes the size of the memory block pointed to by ptr to size bytes.
void* mem_malloc_ts(size_t size);
void mem_free_ts(void* ptr);
void* mem_calloc_ts(size_t nmemb, size_t size);
void* mem_realloc_ts(void* ptr, size_t size);Thread-safe versions of the above functions, protected by a global mutex.
void mem_print_stats(void);Prints current allocator statistics to stdout.
mem_stats_t mem_get_stats(void);Returns a structure containing allocator statistics.
void mem_reset(void);Resets allocator statistics (for testing purposes).
- GCC compiler (or compatible)
- GNU Make
- POSIX-compliant system (Linux, macOS, BSD)
- pthread library
- (Optional) Valgrind for memory debugging
# Build everything (library, tests, benchmarks)
make all
# Build only the library
make liballocator.a
# Build and run tests
make test
# Build and run benchmarks
make bench
# Clean build artifacts
make clean
# Show available targets
make help- Include the header in your code:
#include "allocator.h"- Link against the library:
gcc -o myprogram myprogram.c -L. -lallocator -pthread- Use the allocator functions:
void* ptr = mem_malloc(1024);
// Use the memory...
mem_free(ptr);#include <stdio.h>
#include <string.h>
#include "allocator.h"
int main(void) {
// Allocate memory
char* str = mem_malloc(100);
strcpy(str, "Hello, custom allocator!");
printf("%s\n", str);
// Resize allocation
str = mem_realloc(str, 200);
strcat(str, " It works!");
printf("%s\n", str);
// Free memory
mem_free(str);
// Print statistics
mem_print_stats();
return 0;
}make testThe test suite includes:
- Basic allocation/deallocation tests
- Edge case handling (NULL pointers, zero size, overflow)
- Calloc and realloc functionality
- Large allocation tests (mmap)
- Block coalescing verification
- Block splitting verification
- Thread-safe function tests
make benchBenchmarks compare:
- Custom allocator vs system malloc
- Different allocation sizes
- Allocation/deallocation patterns
- Fragmentation behavior
- calloc and realloc performance
The allocator is designed to work with Valgrind for memory leak detection and debugging.
# Run tests with Valgrind
make valgrind
# Run benchmarks with Valgrind
make valgrind-bench-
Leak Detection: The allocator properly tracks all allocations. Valgrind will report any leaked blocks.
-
Heap Usage: Valgrind shows both brk-based and mmap-based allocations.
-
Invalid Access: Valgrind detects out-of-bounds access and use-after-free errors.
-
Performance Impact: Valgrind significantly slows execution. Benchmark results under Valgrind are not representative of real performance.
-
Known Behaviors:
- Heap blocks from
brk()may show as "still reachable" at program exit (this is normal) - mmap blocks are individually tracked and unmapped
- Free list pointers are properly maintained
- Heap blocks from
==12345== HEAP SUMMARY:
==12345== in use at exit: 0 bytes in 0 blocks
==12345== total heap usage: 1,000 allocs, 1,000 frees, 256,000 bytes allocated
==12345==
==12345== All heap blocks were freed -- no leaks are possible
-
malloc: O(n) where n is the number of free blocks in the searched size classes
- Best case: O(1) when suitable block is at head of free list
- Worst case: O(n) when searching multiple size classes
-
free: O(1) for coalescing and free list insertion
- Constant time with adjacent block checking
-
calloc: O(n) where n is the allocation size (for zeroing)
-
realloc: O(n) where n is the data size to copy
- O(1) if current block is large enough
-
Block header: 40 bytes per allocation (on 64-bit systems)
- Can be optimized for smaller blocks if needed
-
Alignment: 16-byte alignment ensures efficient memory access
-
Internal fragmentation: Minimized by block splitting
-
External fragmentation: Minimized by coalescing
Thread-Unsafe (mem_malloc, etc.):
- Single-threaded applications
- Performance-critical code with external synchronization
- ~10-20% faster due to no mutex overhead
Thread-Safe (mem_malloc_ts, etc.):
- Multi-threaded applications
- Shared memory pools
- When allocations can occur from any thread
#define MIN_BLOCK_SIZE 32 // Minimum block size for splitting
#define ALIGNMENT 16 // Memory alignment boundary
#define NUM_SIZE_CLASSES 10 // Number of segregated lists
#define MMAP_THRESHOLD (128 * 1024) // Use mmap above this size
#define BRK_INCREMENT (64 * 1024) // Heap growth incrementThese can be tuned based on workload characteristics.
- Total bytes allocated
- Total bytes freed
- Current memory usage
- Number of allocations
- Number of frees
- Number of block splits
- Number of block coalesces
- No memory release: Heap memory from
brk()is never returned to the OS - Global state: Not suitable for use in shared libraries (without modifications)
- No NUMA awareness: Assumes uniform memory access
- Fixed size classes: Cannot adapt to workload patterns
- Memory unmapping: Return unused heap pages to OS using
madvise() - Per-thread caches: Reduce lock contention in thread-safe version
- Adaptive size classes: Learn from allocation patterns
- Better large block handling: Red-black tree for large free blocks
- Memory defragmentation: Compact heap during idle time
- SIMD optimization: Use vector instructions for block searching
- Kernel integration: Use more advanced system calls like
madvise()
- API Reference - Detailed function documentation
- Architecture Guide - Design and implementation details
- Valgrind Guide - Memory debugging and leak detection
- Quick Reference - Quick command and API reference
Seyyed Ali Mohammadiyeh (MAX BASE) See LICENSE file for details.