03-堆和栈

news/2025/9/24 13:55:18/文章来源:https://www.cnblogs.com/xiaolujiao/p/19109093

概述

堆和栈是程序运行时内存分配的两个核心区域,用途、管理方式和特性差异很大。且堆(内存区域)与上篇文章的链表(数据结构)有一定关联,但本质不同 —— 堆是一块内存空间,而链表常被用作管理堆内存的工具。下面进行堆栈详细解释:

一、核心概念与特性对比

类型 本质定义 管理方式 核心特性 典型操作效率
栈(Stack) 程序运行时的临时内存区域 编译器自动管理(压栈 / 弹栈) 连续空间、后进先出(LIFO)、大小固定(预设) 极快(O (1),栈指针直接操作)
堆(Heap) 程序运行时的动态内存区域 开发者手动管理(malloc/free 非连续空间、分配 / 释放顺序灵活、大小较大 较慢(O (n),需遍历空闲块)
链表(Linked List) 一种数据结构(节点通过指针连接) 手动通过指针操作(插入 / 删除) 非连续存储、动态增减、节点含数据和指针域

1. 栈的工作原理

栈就像一个 “叠盘子” 的结构:

  • 函数调用时,局部变量、参数、返回地址等被 “压栈”(栈指针向下增长);
  • 函数执行结束时,这些数据被 “弹栈”(栈指针向上回退),内存自动释放。

例如:

void func() {int a = 10;  // a存储在栈上,函数结束后自动释放char b[5] = "abc";  // 数组b也在栈上
}

2. 堆的工作原理

堆是一块内存空间,可以从中分配出一个小buffer,用完后再把它放回去:

  • malloc/calloc申请内存(从堆中分配);
  • free释放内存(归还给堆,否则导致内存泄漏)。

例如:

void func() {int* p = (int*)malloc(sizeof(int));  // 从堆分配4字节*p = 100;  // 堆内存中存储数据free(p);   // 必须手动释放,否则内存泄漏
}

二、堆和链表的关系

堆本身是一块 “原始内存区域”,但操作系统或编译器需要一种方式管理它 ——记录哪些内存是空闲的、哪些已被分配、如何高效分配和回收。而链表是管理堆内存的常用工具

具体来说,堆的管理常使用 “空闲链表(Free List)” 结构:

  • 所有未被分配的空闲内存块,通过链表连接起来(每个空闲块包含 “块大小” 和 “下一个空闲块的指针”);
  • 当调用malloc时,内存管理器遍历空闲链表,找到一块足够大的空闲块,分割后分配给用户,剩余部分仍留在链表中;
  • 当调用free时,内存管理器将释放的块重新加入空闲链表,甚至会合并相邻的空闲块(减少碎片)。

示例:简化的堆管理

假设堆内存初始是一整块空闲区域,用链表记录:

// 空闲块结构(链表节点)
typedef struct FreeBlock {size_t size;  // 空闲块大小struct FreeBlock* next;  // 下一个空闲块
} FreeBlock;// 初始空闲链表(堆的全部空间)
FreeBlock* free_list = (FreeBlock*)HEAP_START;
free_list->size = HEAP_SIZE - sizeof(FreeBlock);  // 减去节点自身大小
free_list->next = NULL;

当分配内存时:

void* my_malloc(size_t need_size) {FreeBlock* cur = free_list;FreeBlock* prev = NULL;// 遍历空闲链表,找足够大的块while (cur && cur->size < need_size) {prev = cur;cur = cur->next;}if (cur) {// 分割空闲块(剩余部分仍为空闲)void* alloc_ptr = cur + 1;  // 跳过链表节点,返回实际数据地址size_t remaining = cur->size - need_size - sizeof(FreeBlock);if (remaining > 0) {// 创建新的空闲块节点FreeBlock* new_free = (FreeBlock*)((char*)alloc_ptr + need_size);new_free->size = remaining;new_free->next = cur->next;// 更新链表连接if (prev) prev->next = new_free;else free_list = new_free;} else {// 空闲块刚好够用,从链表中移除if (prev) prev->next = cur->next;else free_list = cur->next;}return alloc_ptr;}return NULL;  // 内存不足
}

可以看到,堆的分配过程本质是对 “空闲链表” 的操作 —— 这就是堆和链表的核心关联:链表是管理堆内存的 “账本”,让内存分配和释放可追踪。

三、在 RTOS 中的具体应用

RTOS 的核心是 “实时响应” 和 “高效管理任务 / 资源”,堆、栈、链表各司其职,又相互配合。

1. 栈:支撑任务独立运行的 “临时内存”

RTOS 中每个任务都有独立的栈(任务栈),这是栈最核心的应用,原因是:

  • 任务切换时需要保存 / 恢复现场(保存寄存器值、函数返回地址等),这些数据必须存储在任务私有的栈中(也就是为什么创建任务时,会传递栈大小参数,具体见下述实例);
  • 任务执行过程中的局部变量、函数调用参数等临时数据,也存储在自己的栈中。

实例
在 FreeRTOS 中,创建任务时必须指定栈大小(如xTaskCreateusStackDepth参数):

// 任务栈大小为1024字
xTaskCreate(task_func, "Task1", 1024, NULL, 1, &task_handle);
  • 若栈大小不足,任务可能因 “栈溢出” 崩溃(RTOS 通常提供栈溢出检测机制);
  • 栈由编译器自动管理,任务运行时自动分配局部变量,任务切换时 RTOS 内核自动保存栈指针(sp寄存器),确保切换后能恢复执行状态。

2. 堆:动态分配资源的 “共享内存”

RTOS 中堆用于动态创建内核对象(如任务控制块 TCB、消息队列、信号量等),但有严格限制:

  • 标准堆的问题malloc/free可能导致内存碎片(多次分配 / 释放后,空闲内存分散成小块,无法满足大内存申请),且执行时间不确定(遍历空闲块耗时不固定),这与 RTOS 的 “实时性” 冲突。
  • RTOS 的优化方案:多数 RTOS(如 FreeRTOS、uC/OS)提供 “内存池(Memory Pool)” 替代标准堆 —— 预先划分固定大小的内存块,用链表管理(见下文 “链表的作用”),分配 / 释放时间固定(O (1))。

实例
FreeRTOS 的动态内存管理,本质是对堆的封装,但其内部用链表管理空闲块(避免碎片的合并算法):

// 从堆动态创建消息队列(内部调用堆分配函数)
QueueHandle_t xQueue = xQueueCreate(5, sizeof(int)); // 队列可存5个int

3. 链表:管理内核对象的 “高效工具”

链表是 RTOS 内核的 “骨架”,用于组织和快速操作各种对象(任务、消息、定时器等),因双向循环链表的插入 / 删除效率极高(O (1)),完美适配 RTOS 的实时性需求。

核心应用场景

  • 任务管理
    所有任务的 TCB(任务控制块)通过双向循环链表组织,按状态(就绪、阻塞、挂起)分成不同链表。例如 “就绪链表” 按优先级排序,调度器只需从链表头部取最高优先级任务运行:

    // 简化的TCB结构
    typedef struct TCB {int priority;       // 任务优先级void* stack_ptr;    // 任务栈指针struct TCB* prev;   // 前序节点(双向链表)struct TCB* next;   // 后序节点(双向链表)
    } TCB;
    
  • 消息队列
    消息通过链表串联,新消息插入尾部,接收时从头部取出,无需预先分配固定大小的数组,避免空间浪费:

    // 消息节点(链表结构)
    typedef struct MsgNode {void* data;         // 消息数据struct MsgNode* next; // 下一个消息
    } MsgNode;
    
  • 内存池管理
    内存池中的空闲块用链表连接,分配时取链表头部节点,释放时将节点插回链表,操作时间固定:

    // 内存池空闲块(链表节点)
    typedef struct PoolBlock {struct PoolBlock* next; // 下一个空闲块
    } PoolBlock;
    
  • RTOS 的链表设计特点
    几乎所有 RTOS 都用宏定义封装链表操作(如vListInsertuxListRemove),避免重复代码;且链表节点通常嵌入到对象结构体中(如 TCB 包含prev/next指针),而非单独定义,节省内存。

四、RTOS角度三者的关联与区别

关系维度 具体说明
堆与链表 堆的管理依赖链表(如 “空闲链表” 记录未分配内存块),但堆是内存区域,链表是管理工具;RTOS 中内存池用链表管理空闲块,替代标准堆以保证实时性。
栈与堆 栈是任务私有、临时内存(自动管理),堆是全局共享、持久内存(需手动管理);RTOS 中任务栈大小固定,堆(或内存池)用于动态创建内核对象。
链表与栈 / 堆 链表是数据结构,可存储在栈或堆中:RTOS 的静态链表(编译时分配)存储在栈或全局区,动态链表(运行时创建)存储在堆中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/915780.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入解析:Django事务

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

做网站6000左右的电脑网上购物商城系统设计

3.mysql数据库3.10 单表查询3.10.1. 简单查询查询在数据库中使用的频率是最高的&#xff1a;十次查询&#xff0c;一次增删改。1)建表2)插入数据3.10.1.1. 选择字段&#xff1a;selectselect 字段名1,字段名2…… from 表名 where 条件;3.10.1.2. 字段重命名(别名)&#xff1a;…

视频汇聚平台EasyCVR如何构建智慧农业监控监管系统?

视频汇聚平台EasyCVR如何构建智慧农业监控监管系统?现代农业的迅速发展中,集成监控管理系统已成为提高农业生产效率和优化管理的关键工具。EasyCVR视频汇聚平台作为一个具有高度可扩展性、灵活的视频处理能力和便捷的…

一套自用的git提交规范,可清晰的识别到关联的任务/bug - 实践

一套自用的git提交规范,可清晰的识别到关联的任务/bug - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Con…

学做ps的软件的网站有哪些内容石家庄网站建设王道下拉棒

博主介绍&#xff1a;黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者&#xff0c;CSDN博客专家&#xff0c;在线教育专家&#xff0c;CSDN钻石讲师&#xff1b;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程&#xff…

高平市规建设局网站学生个人网站布局

这年头&#xff0c;在职场不但要会做&#xff0c;还要会说。 会说还不能平铺直叙的说&#xff0c;还要能把普通的工作说出话来&#xff0c;这就需要一些“考究”的用词。尤其是在某些头部企业的带领下&#xff0c;业务不够、产品不行、解决方案不够新&#xff0c;就用华丽的辞…

撕开厂商锁定黑箱:MyEMS 如何用开源代码夺回能源管理的 “自主控制权”?

在能源管理数字化浪潮中,许多企业曾满怀期待引入专业系统,却最终陷入深深的困境:系统建成之日,竟是受制于人之时。高昂的许可费、昂贵的定制开发、无休止的升级服务费、封闭的数据格式……这些看似专业的能源管理系…

继续 Vibe Coding 撸工具:Markdown写作 + 一键发布

又是感谢Vibe Coding的一天,最近尝试用 CodyBuddy 来重构一下之前的文章发布工具OpenWrite,经过一周的迭代,现在基本差不多恢复之前80%的功能了。如果你跟我一样,平时写点东西,又讨厌自己的文章被别人搬运,那么可…

C造桥与砍树

链接 题意: 有n个带权的点以及参数k,要求生成一个最小生成树,每个点之间的边权为两个点权之和模k的结果 思路: 对所有权值模k后 发现对于一个权值为val的结点u,链接它的最优结点是 现在还没进入生成树的 (权值最…

基于衍射神经网络的光学高速粒子分类体系A1(未做完)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Keil uVision5 MDK 5.42安装教程(支持ARM Cortex全系列开发)

软件介绍 Keil uVision5 MDK 5.42是ARM公司推出的嵌入式处理器集成开发环境最新版本,专为Cortex-M、Cortex-A、ARM7、ARM9等ARM内核处理器设计。该版本集成ARM Compiler编译器、μVision IDE调试器及Flash烧写模块,支…

2024 ICPC ECfinal E

E. Corrupted Scoreboard Log 大模拟,暴搜。 预处理出 \(0\sim 299\) 和 \(1\sim 100\) 的组合字符串,后续处理出每个 \(\text{try}\) 前面的数字就能得到是哪些组合了,注意 \(\text{22tries}\) 这种还可以拆成 \(\…

自助建子站wordpress粉色主题

1、引言 用SHT30测温湿度&#xff0c;SHT30是I2C通信总线&#xff0c;具体信息去看Datasheet文档&#xff1a;https://pdf1.alldatasheet.com/datasheet-pdf/view/897974/ETC2/SHT30.html。操作系统是Linux&#xff0c;机器是CM3计算板&#xff0c;当然也可以是树莓派和其他主…

从Void到Task<PublishAggregateResult>:一次服务方法返回类型重构的纠结与决策

今天原本想美美地完成UI层与Core层通过消息总线实现博客发布的功能。一切都很顺利,直到我重构到 PublishBlog 方法,准备为ApplicationService 写事件处理逻辑时,不然发现不对劲——“不兑!等等,我该怎么把发布结果…

LVGL移植到STM32F4出现无法运行的问题

跟着网上的教程一步步移植LVGL v8.3到STM32F407VET6上,虽然能成功运行,但是在刷新屏幕可能会出现:只刷新了一部分屏幕 整个屏幕都会卡死查阅了很多资料都没解决,在使用别人的Keil工程时,发现代码的优化等级是-O1,…

网站建设的原因有什么给个网址2021年能看的

环境 Windows 11 家庭中文版git version 2.41.0.windows.1 问题情况 在使用 “命令行终端” 和 “Git Bash” 在本地Git仓库敲击命令时&#xff0c;对中文名称文件显示一连串的数字&#xff0c;如下所示&#xff1a;这种情况通常是由于字符编码设置不正确所引起的 解决办法 设置…

如何选择做网站网易企业邮箱忘记密码

&bc_control spec_bdy_width 此参数指定用于边界过渡的格点总行数&#xff0c;默认值为5。此参数只用于真实大气方案。参数的大小至少为spec_zone 和 relax_zone的和。 spec_zone 指定区域(specified zone)的格点数&#xff0c;默认值为 1。指定边条件时起作用。 relax…

网站地图提交入口班级优化大师电脑版

1,先把报错SQL语句拿出来执行&#xff0c;看看是不是报的这个错 ORA-01830: 日期格式图片在转换整个输入字符串之前结束 2&#xff0c;然后查看默认日期格式是不是“YYYY-MM-DD HH24:MI:SS”&#xff08;正确格式&#xff09;。&#xff1b; 执行&#xff1a; SELECT * FRO…

题目记录(Before NOIP2025 ver)

T1. Beautiful Sequence Unraveling 定义 \(dp_{i,j}\) 表示长度为 \(i\),值域在 \([1,j]\) 之间的好序列的个数。发现好序列不好刻画,所以转化为所有序列的数量减去不是好序列的数量。前者很显然,即 \(i^j\)。接下…

专业修复sqlserver master 数据库损坏。

例如一下错误 在重做数据库“master”中的日志记录操作时,日志记录 ID (57081:184:2) 出错。通常,特定故障以前会在操作系统错误日志中记录为错误。请从完整备份还原数据库,或者修复该数据库。 无法在数据库“maste…