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

栈的概念(韦东山学习笔记)

栈核心问题 (针对 LR 覆盖、局部变量分配、RTOS 任务栈 )

一、知识总览

这部分聚焦栈在函数调用、RTOS 任务中的核心逻辑,解决 3 个关键问题:LR 被覆盖如何处理、局部变量在栈里咋分配、RTOS 为啥每个任务要有独立栈。理解这些,才能搞懂程序执行流程、任务切换的本质,尤其是 RTOS 上下文管理的底层逻辑。

二、核心问题分步拆解

(一)问题 1:LR 被覆盖了怎么办?

在函数嵌套调用场景里,内层函数执行 BL 指令(函数调用指令 )会覆盖外层函数的 LR(Link Register,存储返回地址 ),若不处理,函数返回时执行流向会混乱。结合 上图 拆解解决逻辑:

  1. LR 的作用
    LR 存储函数执行完后要返回的地址。比如 main 调用 a_func 时,执行 BL a_func(图 中 0x080001ba: BL a_func ; 0x8000154 ),会把 main 中下一条指令地址存入 LR,保证 a_func 执行完能回到 main 继续运行。

  2. LR 被覆盖的原因
    函数嵌套调用(如 maina_funcb_func )时,内层函数的 BL 指令会覆盖外层函数的 LR。像 a_func 调用 b_func 执行 BL b_func,会把 a_func 里 “返回给 main 的地址” 覆盖成 “返回给 a_func 的地址”,若不处理,b_func 执行完就无法正确回到 main

  3. 解决方法:栈保存与恢复

    • 压栈保存(函数入口 )
      函数开头用 PUSH 指令存 LR 到栈。以 a_func 为例(图 ),PUSH {r0, lr} 会把当前 R0(通用寄存器 )和 LR(返回地址 )压入栈。这样即便内层函数覆盖 LR,栈里仍留存外层函数正确的返回地址。
    • 出栈恢复(函数出口 )
      函数结尾用 POP 指令恢复 LR 到 PC(Program Counter,控制程序执行流向 )。如 a_func 的 POP {r3, pc},从栈中取出之前保存的 LR 值并赋给 PC,让程序回到正确调用点(如 main )。

    总结:通过 “函数入口 PUSH 存 LR + 函数出口 POP 恢复 PC,借助栈的 “后进先出” 特性,解决多层函数调用时 LR 被覆盖问题,确保函数嵌套调用后执行流能正确回归。图直观呈现了 PUSH 保存 LRPOP 恢复执行流的汇编指令与栈操作对应关系,是理解该机制的关键 。

(二)问题 2:局部变量在栈中如何分配?

函数内的局部变量(如 main 里的 char ch = 65; int i = 99; ),依托栈指针(SP )调整和内存布局规则分配,结合 上 解析:

  1. 栈指针调整:预留空间
    函数执行前,编译器生成指令调整 SP(栈指针 )。以 main 函数为例(上图 ),汇编可能有 SP = SP - 20 操作,向 低地址方向扩展栈帧(ARM 栈通常向低地址增长 ),为局部变量、保存的寄存器预留内存。

  2. 变量填充:规则与对齐
    编译器按 “声明顺序 + 内存对齐” 把局部变量填入栈帧。先声明的变量地址更靠近 SP 原位置(地址 “高” ),后声明的更远离(地址 “低” );同时满足内存对齐(如 int 占 4 字节,按 4 字节对齐存储 )。
    比如上图中,char ch = 65(1 字节 )、int i = 99(4 字节 ),编译器会严格安排它们在栈里的位置,保证访问时能通过 SP + 偏移(如 LDR R0, [SP, #4] )正确读取。

    总结:局部变量分配依托 “调整 SP 预留空间 → 按规则填栈帧 → SP + 偏移 访问” 实现。上图清晰展示 main 函数局部变量在栈中的分配逻辑,是理解该过程的核心参考 。

(三)问题 3:为什么 RTOS 任务都有自己的栈?

RTOS 多任务切换时,需保存 / 恢复任务执行现场(寄存器、局部变量等 ),每个任务配置独立栈是实现稳定切换的基础。结合上 解析:

  1. 任务与执行现场
    RTOS 中任务(如 Task_ATask_B )是独立执行流,需随时暂停、恢复。任务执行现场包含 CPU 寄存器(PCLRR0 - R15 )、局部变量、函数返回地址,统称 “上下文”。

  2. 独立栈的必要性

    • 隔离性:任务切换时,不同任务的栈空间独立,避免数据干扰。比如 Task_A 执行到一半被打断,其局部变量、返回地址存在自己的栈里;Task_B 运行时用自己的栈存数据,互不影响。
    • 保存现场:任务切换时(第 2 张图 ),需把当前任务的 “现场” 存到自己的栈,再恢复下一个任务的 “现场”。若共用一个栈,现场会被覆盖,切换后无法恢复执行。

    举例Task_A 执行 b_func 时被切换,栈里存着 PC = 0x0800017aR0 = 2 等现场(第 3、4 张图 );切换到 Task_B 后,Task_B 用自己的栈运行;切回 Task_A 时,从其栈恢复现场继续执行。

    总结:每个任务独立栈是为实现 “执行现场隔离存储与恢复”,保障多任务切换后能正确续跑。上图直观呈现任务切换时栈对现场的保存 / 恢复逻辑,是理解多任务栈设计的关键 。

三、知识串联(从函数调用到 RTOS 任务 )

  • 函数调用:用 LR 存返回地址,嵌套调用时靠 “压栈保存 LR + 出栈恢复”。解决覆盖问题;局部变量依托 SP 调整、栈帧规则分配。
  • RTOS 任务:每个任务用独立栈保存执行现场(寄存器、返回地址、局部变量 ),切换时存 / 恢复现场,保障任务暂停、续跑的正确性。

四、易错点 & 补充说明

(一)易错点

  1. 栈溢出:函数嵌套深、局部变量大,或 RTOS 任务栈配置小,会耗尽栈空间,覆盖代码段、堆,引发程序崩溃。需合理规划栈大小。
  2. 局部变量地址无效:函数返回后,局部变量栈空间释放,若返回其指针(如 return &ch; ),会因访问 “无效地址” 出错。
  3. 任务栈未对齐:RTOS 任务栈需满足 ARM 架构对齐要求(如 4 字节对齐 ),否则存 / 取数据会因硬件不支持非对齐访问出错。

(二)补充拓展

  • 栈帧优化:编译器会优化栈帧(复用空间、局部变量存寄存器 ),调试时需注意优化可能让栈帧 “不直观”。
  • RTOS 上下文切换细节:除栈外,还涉及 PSP(进程栈指针 )和 MSP(主栈指针 )切换(如 Cortex - M 系列 ),复杂 RTOS 会区分内核栈和任务栈,保障系统调用安全。
http://www.lryc.cn/news/626443.html

相关文章:

  • java17学习笔记-switch总结
  • 服务器硬盘进行分区和挂载
  • 《CDN加速的安全隐患与解决办法:如何构建更安全的网络加速体系》
  • CSDN技术探讨:GEO(生成式引擎优化)如何助力品牌在AI搜索中脱颖而出
  • 有向图(Directed Graph)和有向无环图(Directed Acyclic Graph,DAG)代码实践
  • mRNA 的修饰方式有哪些?它们分别作用于哪些位置?
  • strncpy 函数使用及其模拟实现
  • 医疗AI与医院数据仓库的智能化升级:异构采集、精准评估与高效交互的融合方向(上)
  • Model Context Protocol (MCP) - 尝试创建和使用一下MCP Client
  • 软件测试:如何利用Burp Suite进行高效WEB安全测试
  • 制造业原料仓储混乱?WMS 系统实现物料精准溯源,生产更顺畅_
  • Java 14 新特性及具体应用
  • Spring Boot Controller 使用 @RequestBody + @ModelAttribute 接收请求
  • 应急响应-模拟服务器挂马后的应急相关操作
  • K8S-Pod资源对象
  • Spring Retry实战指南_让你的应用更具韧性
  • 服务器内存使用buff/cache的原理
  • k8s笔记01
  • 自建开发工具IDE(一)之拖找排版—仙盟创梦IDE
  • 跨域问题解决方法
  • 三分钟速通SSH登录
  • IDEA:控制台中文乱码
  • IDEA切换分支时,提示:Git Checkout Problem
  • 用通俗易懂的语言解释前后端分离和不分离的区别及其优缺点
  • 【Java】深入浅出Spring中的@Autowired:自动注入的奥秘
  • 【数据结构】直接选择排序
  • 九、Java类核心语法:构造器、this、封装与static详解
  • rsync 工具
  • Linux 文本处理三剑客:awk、grep、sed 完全指南
  • Redis 安装教程