03-堆和栈

概述

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

一、核心概念与特性对比

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

1. 栈的工作原理

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

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

例如:

/* by yours.tools - online tools website : yours.tools/zh/formatjava.html */
void func() {
    int a = 10;  // a存储在栈上,函数结束后自动释放
    char b[5] = "abc";  // 数组b也在栈上
}

2. 堆的工作原理

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

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

例如:

/* by yours.tools - online tools website : yours.tools/zh/formatjava.html */
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 的静态链表(编译时分配)存储在栈或全局区,动态链表(运行时创建)存储在堆中。
【四轴飞行器】非线性三自由度四轴飞行器模拟器研究(Matlab代码实现)内容概要:本文围绕非线性三自由度四轴飞行器模拟器的研究展开,重点介绍了基于Matlab的建模与仿真方法。通过对四轴飞行器的动力学特性进行分析,构建了非线性状态空间模型,并实现了姿态与位置的动态模拟。研究涵盖了飞行器运动方程的建立、控制系统设计及数值仿真验证等环节,突出非线性系统的精确建模与仿真优势,有助于深入理解飞行器在复杂工况下的行为特征。此外,文中还提到了多种配套技术如PID控制、状态估计与路径规划等,展示了Matlab在航空航天仿真中的综合应用能力。; 适合人群:具备一定自动控制理论基础Matlab编程能力的高校学生、科研人员及从事无人机系统开发的工程技术人员,尤其适合研究生及以上层次的研究者。; 使用场景及目标:①用于四轴飞行器控制系统的设计与验证,支持算法快速原型开发;②作为教学工具帮助理解非线性动力学系统建模与仿真过程;③支撑科研项目中对飞行器姿态控制、轨迹跟踪等问题的深入研究; 阅读建议:建议读者结合文中提供的Matlab代码进行实践操作,重点关注动力学建模与控制模块的实现细节,同时可延伸学习文档中提及的PID控制、状态估计等相关技术内容,以全面提升系统仿真与分析能力。
基础篇-0-Java虚拟机导学课程 11:33 基础篇-1-初识JVM 22:27 基础篇-2-Java虚拟机的组成 04:47 基础篇-3-字节码文件的组成-以正确的姿势打开字节码文件 10:41 基础篇(补)-3.5-字节码文件的组成-基础信息 15:54 基础篇-4-字节码文件的组成-常量池方法 25:51 基础篇-5-字节码文件常见工具的使用1 11:43 基础篇-6-字节码文件常见工具的使用2 22:20 基础篇-7-类的生命周期加载阶段 22:09 基础篇-8-类的生命周期2连接阶段 19:58 基础篇-9-类的生命周期3初始化阶段 26:27 基础篇-10-类加载器的分类 13:56 基础篇-11-启动类加载器 13:36 基础篇-12-扩展应用程序类加载器 16:26 基础篇-13-双亲委派机制 18:43 基础篇-14-打破类的双亲委派机制-自定义类加载器 25:16 基础篇-15-打破双亲委派机制2-线程上下文类加载器 20:17 基础篇-16-打破双亲委派机制3-osgi类的热部署 11:53 基础篇-17-JDK9之后的类加载器 09:05 基础篇-18-运行时数据区-程序计数器 15:42 基础篇-19--局部变量表 19:20 基础篇-20--操作数帧数据 12:08 基础篇-21--内存溢出 15:28 基础篇-22-内存 25:56 基础篇-23-方法区的实现 16:25 基础篇-24-方法区-字符串常量池 20:40 基础篇-25-直接内存 12:39 基础篇-26-自动垃圾回收 11:32 基础篇-27-方法区的回收 11:32 基础篇-28-引用计数法 15:41 基础篇-29-可达性分析法 20:25 基础篇-30-软引用 24:40 基础篇-31-弱虚终结器引用 12:08 基础篇-32-垃圾回收算法的评价标准 13:31 基础篇-33-垃圾回收算法1 10:05 基础篇-34-垃圾回收算法-分代GC 20:19 基础篇-35-垃圾回收器1 15:54 基础篇-36-垃圾回收器2 11:44 基础篇-37-垃圾回收器3 15:51 基础篇-38-g1垃圾回收器 26:23 实战篇-1-内存泄漏内存溢出 21:25 实战篇-2-解决内存泄漏-监控-top命令 12:16 实战篇-3-解决内存泄漏-监控-visualvm 12:50 实战篇-4-解决内存泄漏-监控-arthas tunnel 15:18 实战篇-5-解决内存泄漏-监控-prometheus-grafana 17:53 实战篇-6-解决内存泄漏-内存状况对比 08:39 实战篇-7-解决内存泄漏-内存泄漏产生的几大原因 16:01 实战篇-8-内存泄漏产生的原因2 13:30 实战篇-9-内存泄漏产生的原因3 10:43 实战篇-10-内存泄漏产生的原因4 10:04 实战篇-11-内存泄漏产生原因2-并发请求问题 17:30 实战篇-12-导出内存快照并使用MAT分析 08:38 实战篇-13-MAT内存泄漏检测原理 17:23 实战篇-14-服务器导出内存快照MAT使用小技巧 13:31 实战篇-15-实战1-查询大数据量导致的内存溢出 26:24 实战篇-16-实战2-mybatis导致的内存溢出 10:34 实战篇-17-实战3-k8s容器环境导出大文件内存溢出 26:13 实战篇-18-系统不处理业务时也占用大量的内存 14:13 实战篇-19-文章审核接口的内存问题 18:28 实战篇-20-btracearthas在线定位问题 20:15 实战篇-21-GC调优的核心目标 11:23 实战篇-22-GC调优的常用工具 12:05 实战篇-23-GC调优的常见工具2 14:25 实战篇-24-常见的GC模式 13:38 实战篇-25-基础JVM参数的设置 28:31 实战篇-26-垃圾回收器的选择 18:04 实战篇-27-垃圾回收参数调优 07:56 实战篇-28-实战-GC调优内存调优 30:43 实战篇-29-性能问题的现象解决思路 10:49 实战篇-30-定位进程CPU占用率高的问题 18:52 实战篇-31-接口响应时间很长问题的定位 14:44 实战篇-32-火焰图定位接口响应时间长的问题 12:03 实战篇-33-死锁问题的检测 14:37 实战篇-34-基准测试框架JMH的使用 28:24 实战篇-35-实战-性能调优 26:36 高级篇-01-GraalVM介绍 12:13 高级篇-02-GraalVM的两种运行模式 15:43 高级篇-03-使用SpringBoot3构建GraalVM应用 15:08 高级篇-04-将GraalVM应用部署到函数计算 25:13 高级篇-05-将GraalVM应用部署到Serverless 09:14 高级篇-06-参数优化故障诊断 22:31 高级篇-07-垃圾回收器的技术演进 13:09 高级篇-08-ShenandoahGC 22:50 高级篇-09-ZGC 14:35 高级篇-10-实战案例-内存不足时的垃圾回收测试 09:47 高级篇-11-JavaAgent技术 12:16 高级篇-12-JavaAgent环境搭建 15:24 高级篇-13-查看内存的使用情况 18:48 高级篇-14-生成内存快照 13:47 高级篇-15-获取类加载器的信息 16:26 高级篇-16-打印类的源码 18:00 高级篇-17-使用ASM增强方法 29:45 高级篇-18-使用ByteBuddy打印方法执行的参数耗时 21:55 高级篇-19-APM系统数据采集 24:30 原理篇-01-上的数据存储 15:05 原理篇-02-boolean在上的存储方式 22:48 原理篇-03-对象在上的存储1 17:27 原理篇-04-对象在上的存储2 25:14 原理篇-05-方法调用的原理1-静态绑定 19:26 原理篇-06-方法调用的原理2-动态绑定 15:25 原理篇-07-异常捕获的原理 12:00 原理篇-08-JIT即时编译器 14:49 原理篇-09-JIT即时编译器优化手段1-方法内联 16:49 原理篇-10-JIT即时编译器优化手段2-逃逸分析 09:03 原理篇-11-g1垃圾回收器原理-年轻代回收 27:57 原理篇-12-g1垃圾回收器原理-混合回收 17:24 原理篇-13-ZGC原理 26:27 原理篇-14-ShenandoahGC原理 09:39 面试篇-01-什么是JVM 16:38 面试篇-02-字节码文件的组成 15:02 面试篇-03-什么是运行时数据区 20:09 面试篇-04-哪些区域会出现内存溢出 11:56 面试篇-05-JDK6-8内存区域上的不同 14:36 面试篇-06-类的生命周期 17:17 面试篇-07-什么是类加载器 17:05 面试篇-08-什么是双亲委派机制 12:15 面试篇-09-如何打破双亲委派机制 18:10 面试篇-10-tomcat的自定义类加载器 31:18 面试篇-11-如何判断上的对象有没有被引用 10:05 面试篇-12-JVM中都有哪些引用类型 16:58 面试篇-13-theadlocal中为什么要使用弱引用 12:16 面试篇-14-有哪些垃圾回收算法 24:54 面试篇-15-有哪些常用的垃圾回收器 18:55 面试篇-16-如何解决内存泄漏问题 23:52 面试篇-17-常见的JVM参数 11:11 这是目前我学习的视频集合,要不要全看,或者少了什么,有哪些重要内容需要进行学习汇总或刷题或通过小实例验证
10-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值