The "Holy Bible" for embedded engineers
Memory fragmentation occurs when free memory becomes divided into small, non-contiguous blocks, making it difficult to allocate larger chunks. In embedded systems, this can lead to allocation failures even when sufficient total free memory exists.
Memory fragmentation is a phenomenon where the available free memory in a system becomes divided into small, non-contiguous blocks, even though the total amount of free memory might be sufficient for a requested allocation.
Initial Memory State:
┌─────────────────────────────────────────────────────────────┐
│ Free Memory (1MB) │
└─────────────────────────────────────────────────────────────┘
After Some Allocations:
┌─────────┬─────────┬─────────┬─────────┬─────────┬───────────┐
│ Alloc A │ Alloc B │ Alloc C │ Alloc D │ Alloc E │ Free │
│ (100B) │ (200B) │ (150B) │ (300B) │ (250B) │ (1MB) │
└─────────┴─────────┴─────────┴─────────┴─────────┴───────────┘
After Freeing B and D:
┌─────────┬─────────┬─────────┬─────────┬─────────┬───────────┐
│ Alloc A │ FREE │ Alloc C │ FREE │ Alloc E │ Free │
│ (100B) │ (200B) │ (150B) │ (300B) │ (250B) │ (1MB) │
└─────────┴─────────┴─────────┴─────────┴─────────┴───────────┘
Fragmented State:
┌─────────┬─────────┬─────────┬─────────┬─────────┬───────────┐
│ Alloc A │ FREE │ Alloc C │ FREE │ Alloc E │ Free │
│ (100B) │ (200B) │ (150B) │ (300B) │ (250B) │ (1MB) │
└─────────┴─────────┴─────────┴─────────┴─────────┴───────────┘
↑ ↑ ↑ ↑ ↑
Cannot allocate 400B despite having 1.5MB free!
High Impact Scenarios:
Low Impact Scenarios:
External fragmentation occurs when free memory is scattered in small pieces throughout the heap, making it impossible to allocate large contiguous blocks even when sufficient total free memory exists.
Characteristics:
Example Scenario:
Memory State:
┌─────────┬─────────┬─────────┬─────────┬─────────┬───────────┐
│ Alloc A │ FREE │ Alloc B │ FREE │ Alloc C │ FREE │
│ (100B) │ (200B) │ (150B) │ (300B) │ (250B) │ (400B) │
└─────────┴─────────┴─────────┴─────────┴─────────┴───────────┘
Problem: Cannot allocate 500B despite having 900B free!
Internal fragmentation occurs when allocated memory is larger than requested due to alignment requirements, allocation granularity, or memory management overhead.
Characteristics:
Example Scenario:
Request: 1 byte
Allocated: 8 bytes (due to alignment)
Wasted: 7 bytes (internal fragmentation)
Non-compacting Allocators:
Compacting Allocators:
Sequential Allocation Pattern:
1. Allocate A (100B)
2. Allocate B (200B)
3. Allocate C (150B)
4. Allocate D (300B)
5. Free B
6. Free D
7. Try to allocate 400B → FAILS!
Random Allocation Pattern:
1. Allocate blocks of varying sizes
2. Free blocks in random order
3. Creates scattered free memory
4. Large allocations become difficult
Fragmentation Ratio:
Fragmentation Ratio = (Largest Free Block) / (Total Free Memory) × 100%
Fragmentation Index:
Fragmentation Index = 1 - (Largest Free Block) / (Total Free Memory)
Track all memory allocations and deallocations to analyze fragmentation patterns.
typedef struct {
void* start;
size_t size;
bool is_free;
uint32_t allocation_id;
} memory_block_t;
typedef struct {
memory_block_t* blocks;
size_t block_count;
size_t max_blocks;
size_t total_allocated;
size_t total_free;
} fragmentation_tracker_t;
fragmentation_tracker_t* create_fragmentation_tracker(size_t max_blocks) {
fragmentation_tracker_t* tracker = malloc(sizeof(fragmentation_tracker_t));
if (!tracker) return NULL;
tracker->blocks = calloc(max_blocks, sizeof(memory_block_t));
tracker->block_count = 0;
tracker->max_blocks = max_blocks;
tracker->total_allocated = 0;
tracker->total_free = 0;
return tracker;
}
void track_allocation(fragmentation_tracker_t* tracker, void* ptr, size_t size) {
if (tracker->block_count < tracker->max_blocks) {
tracker->blocks[tracker->block_count].start = ptr;
tracker->blocks[tracker->block_count].size = size;
tracker->blocks[tracker->block_count].is_free = false;
tracker->blocks[tracker->block_count].allocation_id = tracker->block_count;
tracker->block_count++;
tracker->total_allocated += size;
}
}
Analyze memory layout to detect fragmentation patterns.
typedef struct {
size_t largest_free_block;
size_t total_free_memory;
size_t free_block_count;
float fragmentation_ratio;
float fragmentation_index;
} fragmentation_analysis_t;
fragmentation_analysis_t analyze_fragmentation(fragmentation_tracker_t* tracker) {
fragmentation_analysis_t analysis = {0};
// Find largest free block and count free blocks
for (size_t i = 0; i < tracker->block_count; i++) {
if (tracker->blocks[i].is_free) {
analysis.total_free_memory += tracker->blocks[i].size;
analysis.free_block_count++;
if (tracker->blocks[i].size > analysis.largest_free_block) {
analysis.largest_free_block = tracker->blocks[i].size;
}
}
}
// Calculate fragmentation metrics
if (analysis.total_free_memory > 0) {
analysis.fragmentation_ratio = (float)analysis.largest_free_block /
analysis.total_free_memory * 100.0f;
analysis.fragmentation_index = 1.0f -
(float)analysis.largest_free_block /
analysis.total_free_memory;
}
return analysis;
}
Monitor fragmentation in real-time to detect issues early.
typedef struct {
size_t allocation_count;
size_t deallocation_count;
size_t failed_allocations;
size_t peak_memory_usage;
fragmentation_analysis_t current_analysis;
} fragmentation_monitor_t;
void update_fragmentation_monitor(fragmentation_monitor_t* monitor,
fragmentation_analysis_t* analysis) {
monitor->current_analysis = *analysis;
// Alert if fragmentation is high
if (analysis->fragmentation_index > 0.8f) {
printf("WARNING: High fragmentation detected (%.1f%%)\n",
analysis->fragmentation_index * 100.0f);
}
}
Use memory pools to avoid fragmentation by pre-allocating fixed-size blocks.
Benefits:
Use Cases:
Use buddy system allocation to minimize fragmentation.
Characteristics:
Use slab allocation for frequently allocated objects.
Characteristics:
First Fit:
Best Fit:
Move allocated blocks to coalesce free memory.
Process:
Challenges:
Automatic memory management with compaction.
Types:
Considerations:
Application-controlled defragmentation.
Approach:
Benefits:
Pre-allocate memory in fixed-size blocks to avoid fragmentation.
typedef struct {
void* pool_start;
size_t block_size;
size_t block_count;
void** free_list;
size_t free_count;
} fixed_size_pool_t;
fixed_size_pool_t* create_fixed_size_pool(size_t block_size, size_t block_count) {
fixed_size_pool_t* pool = malloc(sizeof(fixed_size_pool_t));
if (!pool) return NULL;
// Allocate pool memory
pool->pool_start = malloc(block_size * block_count);
if (!pool->pool_start) {
free(pool);
return NULL;
}
// Initialize pool structure
pool->block_size = block_size;
pool->block_count = block_count;
pool->free_count = block_count;
// Initialize free list
pool->free_list = malloc(block_count * sizeof(void*));
if (!pool->free_list) {
free(pool->pool_start);
free(pool);
return NULL;
}
// Link all blocks in free list
for (size_t i = 0; i < block_count; i++) {
pool->free_list[i] = (char*)pool->pool_start + (i * block_size);
}
return pool;
}
Use multiple pools for different block sizes.
typedef struct {
fixed_size_pool_t* pools;
size_t pool_count;
size_t* block_sizes;
} multi_pool_t;
multi_pool_t* create_multi_pool(size_t* block_sizes, size_t* block_counts, size_t pool_count) {
multi_pool_t* mp = malloc(sizeof(multi_pool_t));
if (!mp) return NULL;
mp->pools = malloc(pool_count * sizeof(fixed_size_pool_t*));
mp->block_sizes = malloc(pool_count * sizeof(size_t));
mp->pool_count = pool_count;
if (!mp->pools || !mp->block_sizes) {
free(mp->pools);
free(mp->block_sizes);
free(mp);
return NULL;
}
// Create pools for each block size
for (size_t i = 0; i < pool_count; i++) {
mp->block_sizes[i] = block_sizes[i];
mp->pools[i] = create_fixed_size_pool(block_sizes[i], block_counts[i]);
if (!mp->pools[i]) {
// Cleanup on failure
for (size_t j = 0; j < i; j++) {
destroy_fixed_size_pool(mp->pools[j]);
}
free(mp->pools);
free(mp->block_sizes);
free(mp);
return NULL;
}
}
return mp;
}
Memory pools provide predictable allocation times.
Benefits:
Pre-allocate memory to avoid runtime allocation.
Approach:
Monitor fragmentation in real-time systems.
Metrics:
typedef struct {
void* start;
size_t size;
bool is_free;
} memory_block_t;
typedef struct {
memory_block_t* blocks;
size_t block_count;
size_t max_blocks;
} fragmentation_tracker_t;
fragmentation_tracker_t* create_fragmentation_tracker(size_t max_blocks) {
fragmentation_tracker_t* tracker = malloc(sizeof(fragmentation_tracker_t));
if (!tracker) return NULL;
tracker->blocks = calloc(max_blocks, sizeof(memory_block_t));
tracker->block_count = 0;
tracker->max_blocks = max_blocks;
return tracker;
}
void track_allocation(fragmentation_tracker_t* tracker, void* ptr, size_t size) {
if (tracker->block_count < tracker->max_blocks) {
tracker->blocks[tracker->block_count].start = ptr;
tracker->blocks[tracker->block_count].size = size;
tracker->blocks[tracker->block_count].is_free = false;
tracker->block_count++;
}
}
typedef struct {
size_t largest_free_block;
size_t total_free_memory;
size_t free_block_count;
float fragmentation_ratio;
} fragmentation_analysis_t;
fragmentation_analysis_t analyze_fragmentation(fragmentation_tracker_t* tracker) {
fragmentation_analysis_t analysis = {0};
for (size_t i = 0; i < tracker->block_count; i++) {
if (tracker->blocks[i].is_free) {
analysis.total_free_memory += tracker->blocks[i].size;
analysis.free_block_count++;
if (tracker->blocks[i].size > analysis.largest_free_block) {
analysis.largest_free_block = tracker->blocks[i].size;
}
}
}
if (analysis.total_free_memory > 0) {
analysis.fragmentation_ratio = (float)analysis.largest_free_block /
analysis.total_free_memory * 100.0f;
}
return analysis;
}
typedef struct {
void* pool_start;
size_t block_size;
size_t block_count;
void** free_list;
size_t free_count;
} fixed_size_pool_t;
void* pool_alloc(fixed_size_pool_t* pool) {
if (pool->free_count == 0) {
return NULL; // Pool exhausted
}
void* block = pool->free_list[--pool->free_count];
return block;
}
void pool_free(fixed_size_pool_t* pool, void* ptr) {
if (pool->free_count < pool->block_count) {
pool->free_list[pool->free_count++] = ptr;
}
}
Problem: Not monitoring fragmentation in long-running systems Solution: Implement fragmentation monitoring and alerts
// Monitor fragmentation regularly
void check_fragmentation(fragmentation_tracker_t* tracker) {
fragmentation_analysis_t analysis = analyze_fragmentation(tracker);
if (analysis.fragmentation_ratio < 50.0f) {
printf("WARNING: High fragmentation detected (%.1f%%)\n",
100.0f - analysis.fragmentation_ratio);
}
}
Problem: Random allocation and deallocation patterns Solution: Use structured allocation patterns
// Use LIFO allocation pattern when possible
typedef struct {
void* blocks[MAX_BLOCKS];
size_t count;
} lifo_allocator_t;
void* lifo_alloc(lifo_allocator_t* allocator) {
if (allocator->count > 0) {
return allocator->blocks[--allocator->count];
}
return NULL;
}
void lifo_free(lifo_allocator_t* allocator, void* ptr) {
if (allocator->count < MAX_BLOCKS) {
allocator->blocks[allocator->count++] = ptr;
}
}
Problem: Unfreed memory creates permanent fragmentation Solution: Implement memory leak detection
// Track allocations for leak detection
typedef struct {
void* ptr;
size_t size;
const char* file;
int line;
} allocation_info_t;
void* debug_malloc(size_t size, const char* file, int line) {
void* ptr = malloc(size);
if (ptr) {
track_allocation(ptr, size, file, line);
}
return ptr;
}
void debug_free(void* ptr) {
if (ptr) {
track_deallocation(ptr);
free(ptr);
}
}
Problem: Memory pools too small for application needs Solution: Analyze memory usage patterns and size pools accordingly
// Analyze memory usage to size pools
typedef struct {
size_t size;
size_t count;
size_t peak_usage;
} memory_usage_pattern_t;
void analyze_memory_usage(memory_usage_pattern_t* patterns, size_t pattern_count) {
// Analyze allocation patterns to determine pool sizes
for (size_t i = 0; i < pattern_count; i++) {
printf("Size %zu: %zu allocations, peak %zu\n",
patterns[i].size, patterns[i].count, patterns[i].peak_usage);
}
}
Next Steps: Explore Memory Pool Allocation to understand how memory pools prevent fragmentation, or dive into Memory Management for broader memory management techniques.