当前位置: 首页 > news >正文

内存分配实战:用C语言手把手实现首次适应算法(附完整代码)

内存分配实战:用C语言手把手实现首次适应算法(附完整代码)

在操作系统内存管理的众多算法中,首次适应算法因其简单高效而广受青睐。本文将带你从零开始,用C语言完整实现这一经典算法。不同于纸上谈兵的理论讲解,我们将通过可运行的代码示例,深入探讨双向链表在内存管理中的应用,以及如何处理内存分配与回收中的各种边界情况。

1. 首次适应算法核心原理

首次适应算法(First-Fit Algorithm)是动态分区分配中最基础的策略之一。它的工作原理可以概括为:

  • 顺序搜索:从内存低地址开始,依次检查每个空闲分区
  • 首次匹配:选择第一个足够大的空闲分区进行分配
  • 分割剩余:如果分区大于需求,将剩余部分作为新的空闲分区

这种算法的优势在于实现简单,搜索速度快。但缺点也明显:低地址端容易产生大量小碎片,而大空闲分区往往集中在内存高地址端。

关键数据结构我们使用带头节点的双向链表来管理内存分区:

struct areaNode { int ID; // 分区标识 int size; // 分区大小 int address; // 起始地址 int flag; // 使用状态(0:空闲 1:已分配) }; typedef struct DuNode { struct areaNode data; struct DuNode *prior; struct DuNode *next; }*DuLinkList;

2. 内存分配函数实现

首次适应算法的分配过程需要处理以下关键步骤:

  1. 遍历空闲分区链,寻找第一个满足大小的分区
  2. 精确匹配时直接分配整个分区
  3. 分区较大时分割出所需大小,剩余部分保留
  4. 更新链表指针关系

以下是核心代码实现:

bool first_fit(int id, int m_size) { DuLinkList p = m_head; while(p != m_last) { DuLinkList n = p->next; if(!n->data.flag && n->data.size >= m_size) { // 创建新节点记录分配信息 DuLinkList t = new DuNode(); t->data.ID = id; t->data.size = m_size; t->data.address = n->data.address; t->data.flag = 1; // 调整剩余空间 n->data.address += m_size; n->data.size -= m_size; // 插入链表 t->prior = p; t->next = n; p->next = t; n->prior = t; return true; } p = n; } return false; // 分配失败 }

注意:实际工程中应添加参数校验,如检查m_size是否为正数,id是否合法等

3. 内存回收的四种边界情况

内存回收比分配更为复杂,需要处理四种可能的邻接情况。我们通过图示和代码结合的方式详细说明:

3.1 与前空闲分区相邻

[F1空闲][回收区] → 合并到F1

代码处理:

if(prev->data.flag == 0) { prev->data.size += curr->data.size; prev->next = curr->next; curr->next->prior = prev; delete curr; }

3.2 与后空闲分区相邻

[回收区][F2空闲] → 合并到F2

代码处理:

if(next->data.flag == 0) { next->data.address = curr->data.address; next->data.size += curr->data.size; curr->prior->next = next; next->prior = curr->prior; delete curr; }

3.3 同时与前后空闲分区相邻

[F1空闲][回收区][F2空闲] → 三者合并

代码处理:

if(prev->data.flag == 0 && next->data.flag == 0) { prev->data.size += curr->data.size + next->data.size; prev->next = next->next; next->next->prior = prev; delete curr; delete next; }

3.4 独立不邻接

[已分配][回收区][已分配] → 创建新空闲项

代码处理:

curr->data.flag = 0; curr->data.ID = 0; // 保持链表原有结构不变

4. 完整代码实现与测试

以下是整合后的完整实现,包含初始化、分配、回收和打印功能:

#include <stdio.h> #include <stdlib.h> const int Max_length = 55; // 内存总大小 // 分区结构体定义 struct areaNode { int ID; int size; int address; int flag; }; // 双向链表节点 typedef struct DuNode { struct areaNode data; struct DuNode *prior; struct DuNode *next; }*DuLinkList; DuLinkList m_head = new DuNode, m_last = new DuNode; void init() { m_head->prior = NULL; m_head->next = m_last; m_last->prior = m_head; m_last->next = NULL; m_head->data.size = 0; m_last->data.address = 0; m_last->data.size = Max_length; m_last->data.ID = 0; m_last->data.flag = 0; } void show() { DuNode *p = m_head->next; printf("+++++++++++++++++++++++++++++++++++++++\n"); while (p) { printf("分区号:%s\n", p->data.ID == 0 ? "FREE" : p->data.ID); printf("起始地址:%d\n", p->data.address); printf("大小:%d KB\n", p->data.size); printf("状态:%s\n", p->data.flag ? "已分配" : "空闲"); printf("——————————————————\n"); p = p->next; } } // 首次适应分配算法(前面已给出,此处省略) void recycle(int id) { DuLinkList p = m_head; while(p != m_last) { DuLinkList curr = p->next; if(curr->data.ID == id) { curr->data.flag = 0; curr->data.ID = 0; DuLinkList prev = curr->prior; DuLinkList next = curr->next; // 检查前驱节点是否空闲 if(prev != m_head && prev->data.flag == 0) { prev->data.size += curr->data.size; prev->next = next; next->prior = prev; delete curr; curr = prev; // 当前节点指向前驱 } // 检查后继节点是否空闲 if(next != m_last && next->data.flag == 0) { curr->data.size += next->data.size; curr->next = next->next; next->next->prior = curr; delete next; } return; } p = curr; } } int main() { init(); printf("初始状态:\n"); show(); printf("\n分配作业1请求15KB:\n"); first_fit(1, 15); show(); printf("\n分配作业2请求30KB:\n"); first_fit(2, 30); show(); printf("\n释放作业1的15KB:\n"); recycle(1); show(); printf("\n分配作业3请求8KB:\n"); first_fit(3, 8); show(); printf("\n分配作业4请求6KB:\n"); first_fit(4, 6); show(); printf("\n释放作业2的30KB:\n"); recycle(2); show(); // 清理链表 DuNode *p = m_head; while(p != NULL) { DuNode *temp = p; p = p->next; delete temp; } return 0; }

5. 算法优化与扩展建议

虽然基础实现已经完整,但在实际项目中还可以考虑以下优化:

  1. 碎片整理:定期合并相邻小碎片

    void defragment() { DuLinkList p = m_head->next; while(p != m_last) { DuLinkList next = p->next; if(!p->data.flag && !next->data.flag) { p->data.size += next->data.size; p->next = next->next; next->next->prior = p; delete next; } else { p = p->next; } } }
  2. 分配策略改进

    • 下次适应:记录上次查找位置,减少低地址碎片
    • 快速适应:维护多个空闲链表按大小分类
  3. 内存保护机制

    • 添加越界检查
    • 实现内存访问权限控制
  4. 可视化工具

    void visual() { DuLinkList p = m_head->next; while(p != m_last) { printf("|%3dK %s", p->data.size, p->data.flag ? "[已分配]" : "[空闲]"); p = p->next; } printf("\n"); }

在实际项目中使用这种内存管理方案时,建议添加完善的日志系统记录分配/释放操作,这对调试内存相关问题非常有帮助。

http://www.jsqmd.com/news/490264/

相关文章:

  • 2026支付宝立减金回收全指南:从渠道选择到常见问题解答 - 团团收购物卡回收
  • 实战攻坚:用快马平台生成能应对反爬策略的clawx高级爬虫
  • B+树索引 vs 哈希索引:用Student表案例详解5种数据库查询原理
  • 2026年工厂短视频推广避坑指南:本地化服务如何破解排名陷阱 - 精选优质企业推荐榜
  • 2026登高车品牌推荐,车载登高车多少钱一台你知道吗 - myqiye
  • 数字证书在PKI体系中的核心作用与实战解析
  • 2026年香港审计公司综合测评榜单:前五强深度解析与选型指南 - 小白条111
  • 工控机配置dhcp server,绑定指定网口,不报错服务不重启、开机自启、不插网线也能用的 dhcp 完整配置
  • 衡山派D133EBS开发板模块移植手册:基于RT-Thread与Luban-lite的官方指南
  • 2026年沈阳钢材拉弯加工厂费用排行,哪家价格合理 - 工业设备
  • 基于TI TMS320F28P550的光敏电阻传感器模块移植与ADC/GPIO驱动实战
  • 2026年工厂短视频推广避坑指南:本地化服务如何破解制作陷阱 - 精选优质企业推荐榜
  • 立创开源四开关BUCK-BOOST数字电源开发板(STM32G474核心)硬件设计与功能解析
  • 讲讲硬质合金材料厂家,湖南博云东方粉末冶金值得推荐吗 - 工业品牌热点
  • 有哪些本地上门手表回收平台,性价比高的推荐 - 工业推荐榜
  • 新手如何借助快马平台轻松上手智能车竞赛嵌入式开发
  • EasyAnimateV5模型量化部署:TensorRT加速实战
  • 2026年工厂短视频推广避坑指南:本地化服务如何破解制作痛点 - 精选优质企业推荐榜
  • bert-base-chinese预训练模型新手教程:完型填空、语义相似度、特征提取全解析
  • 【Linux系统】万字解析,进程间的信号
  • 正德会计服务质量如何,专业团队保障审计结果? - mypinpai
  • Phi-3-vision-128k-instruct开发者案例:跨境电商多语言商品图理解
  • FLUX.1游戏开发:Unity插件实现场景自动生成
  • Qwen3-14b_int4_awq性能实测报告:吞吐量、首token延迟、e2e响应时间分析
  • 家人们谁懂啊
  • Phi-3-vision-128k-instruct效果展示:实验室设备图→操作规范+安全风险+维护周期
  • 突破网络限制的小说下载解决方案:Tomato-Novel-Downloader全平台离线阅读方案
  • Lumafly:实现模组无缝管理的跨平台解决方案 - 空洞骑士玩家的效率提升工具
  • AI头像生成器实战案例:为小红书/微信/B站定制风格化头像的完整方案
  • 盒马鲜生购物卡回收避坑指南:这 5 个坑千万别踩! - 团团收购物卡回收