用C语言手把手实现动态分区管理:从首次适应算法到内存回收的完整代码解析

张开发
2026/4/6 12:30:22 15 分钟阅读

分享文章

用C语言手把手实现动态分区管理:从首次适应算法到内存回收的完整代码解析
用C语言手把手实现动态分区管理从首次适应算法到内存回收的完整代码解析在操作系统的内存管理模块中动态分区分配是最基础也最经典的算法之一。不同于固定分区分配的僵化动态分区能根据进程实际需求灵活划分内存空间显著提高资源利用率。本文将带你用C语言完整实现一个动态分区管理器重点剖析首次适应算法的实现细节并深入讲解内存回收时的四种边界情况处理。1. 动态分区管理的核心数据结构实现动态分区管理器的第一步是设计合适的数据结构。我们选择带头结点的双向链表来维护空闲内存分区这是因为它能高效支持前驱和后继节点的访问非常适合内存合并的场景。#include stdio.h #include stdlib.h const int MAX_MEMORY 55; // 模拟55MB物理内存 typedef struct { int id; // 分区标识0表示空闲 int size; // 分区大小MB int start_addr; // 起始地址 int is_used; // 使用状态0-空闲1-已分配 } MemoryBlock; typedef struct Node { MemoryBlock block; struct Node *prev; struct Node *next; } MemoryNode; MemoryNode *head, *tail; // 全局链表头尾指针这个设计有几个关键点值得注意MemoryBlock结构体封装了内存块的所有元信息双向链表节点MemoryNode包含前后指针便于插入和删除操作全局的head和tail指针维护链表边界其中head是哨兵节点初始化函数需要创建这个链表结构void init_memory() { head (MemoryNode*)malloc(sizeof(MemoryNode)); tail (MemoryNode*)malloc(sizeof(MemoryNode)); head-prev NULL; head-next tail; tail-prev head; tail-next NULL; // 初始化整个内存空间为空闲块 tail-block.id 0; tail-block.size MAX_MEMORY; tail-block.start_addr 0; tail-block.is_used 0; }2. 首次适应算法的实现细节首次适应算法First-Fit的核心思想是从内存低地址开始搜索找到第一个能满足需求的空闲分区就立即分配。这种算法实现简单但容易在低地址产生碎片。2.1 内存分配流程int allocate_memory(int process_id, int size) { MemoryNode *current head-next; while (current ! tail) { if (!current-block.is_used current-block.size size) { // 创建新节点表示已分配块 MemoryNode *new_node (MemoryNode*)malloc(sizeof(MemoryNode)); new_node-block.id process_id; new_node-block.size size; new_node-block.start_addr current-block.start_addr; new_node-block.is_used 1; // 调整剩余空闲空间 current-block.start_addr size; current-block.size - size; // 插入新节点到链表 new_node-prev current-prev; new_node-next current; current-prev-next new_node; current-prev new_node; return new_node-block.start_addr; } current current-next; } return -1; // 分配失败 }这段代码有几个值得注意的技术细节搜索从head-next开始跳过了哨兵节点当找到合适空闲块时会将其分割为已分配部分和剩余部分剩余部分继续保留在链表中地址自动调整为start_addr size函数返回分配的内存起始地址-1表示失败2.2 两种实现方式的对比原始内容中展示了两种实现方式它们在处理零碎空间时有明显差异特性方法一方法二空闲块处理保留剩余空间当剩余为0时删除节点节点插入位置在匹配块前插入新节点直接修改匹配块参数代码复杂度较简单需要处理更多边界条件内存利用率可能产生更多小碎片相对更紧凑方法二在检测到剩余空间为零时会自动删除节点这虽然增加了代码复杂度但能保持链表更简洁。在实际系统开发中这种处理方式往往更受欢迎。3. 内存回收的四种边界情况内存回收是动态分区管理中最复杂的部分需要处理四种可能的邻接情况。我们先看基础代码框架void free_memory(int process_id) { MemoryNode *current head-next; while (current ! tail) { if (current-block.is_used current-block.id process_id) { current-block.is_used 0; current-block.id 0; // 情况处理将在这里添加 merge_adjacent_blocks(current); return; } current current-next; } }3.1 四种合并情况的处理void merge_adjacent_blocks(MemoryNode *block) { // 前驱节点是空闲块 if (block-prev ! head !block-prev-block.is_used) { block-prev-block.size block-block.size; block-prev-next block-next; block-next-prev block-prev; MemoryNode *to_free block; block block-prev; free(to_free); } // 后继节点是空闲块 if (block-next ! tail !block-next-block.is_used) { block-block.size block-next-block.size; MemoryNode *to_free block-next; block-next to_free-next; to_free-next-prev block; free(to_free); } }这四种情况可以简化为两个主要操作与前驱合并当释放块的前一个节点也是空闲时合并它们与后继合并当释放块的后一个节点也是空闲时合并它们注意合并操作必须同时更新链表指针和内存块大小并确保不会误操作头尾哨兵节点。4. 可视化与调试技巧为了验证我们的实现是否正确需要一个能直观展示内存状态的方法。以下是改进版的打印函数void print_memory_map() { printf(\n内存当前状态\n); printf(----------------------------------------\n); printf(| ID | 起始地址 | 大小(MB) | 状态 |\n); printf(----------------------------------------\n); MemoryNode *current head-next; while (current ! tail) { printf(| %-4d | %-10d | %-10d | %-8s |\n, current-block.id, current-block.start_addr, current-block.size, current-block.is_used ? 已分配 : 空闲); current current-next; } printf(----------------------------------------\n); }调试动态内存管理时常见的问题包括内存泄漏忘记释放已合并的链表节点野指针合并后未正确更新相邻节点的指针边界错误未正确处理头尾哨兵节点的特殊情况一个实用的调试技巧是在每次操作后打印内存状态// 示例测试流程 int main() { init_memory(); print_memory_map(); // 初始状态 allocate_memory(1, 15); print_memory_map(); // 分配作业1 allocate_memory(2, 30); print_memory_map(); // 分配作业2 free_memory(1); print_memory_map(); // 释放作业1 // 更多测试用例... return 0; }5. 从实验到实践的延伸虽然我们的实现已经能正确处理基本场景但真实的操作系统内存管理器还要考虑更多因素碎片整理定期合并空闲块减少外部碎片分配策略优化最佳适应Best-Fit最坏适应Worst-Fit快速适应Quick-Fit多线程安全添加锁机制保证线程安全虚拟内存集成与分页/分段机制协同工作以下是一个简单的碎片整理实现void defragment_memory() { MemoryNode *current head-next; int free_space 0; // 计算总空闲空间 while (current ! tail) { if (!current-block.is_used) { free_space current-block.size; MemoryNode *to_free current; current current-next; // 从链表中移除空闲块 to_free-prev-next to_free-next; to_free-next-prev to_free-prev; free(to_free); } else { current current-next; } } // 在末尾创建一个大的空闲块 if (free_space 0) { MemoryNode *new_free (MemoryNode*)malloc(sizeof(MemoryNode)); new_free-block.id 0; new_free-block.size free_space; new_free-block.is_used 0; new_free-block.start_addr tail-prev-block.start_addr tail-prev-block.size; // 插入到链表末尾 new_free-prev tail-prev; new_free-next tail; tail-prev-next new_free; tail-prev new_free; } }在实际项目中内存管理器往往需要与硬件特性紧密结合。比如在嵌入式系统中可能需要考虑内存对齐要求如4字节对齐特殊内存区域的保护如DMA区域电源管理相关的内存操作

更多文章