c语言栈溢出的原因及解决办法_STM32编程:是时候深入理解栈了

2023-05-16


[导读] 从这篇文章开始,将会不定期更新关于嵌入式C语言编程相关的个人认为比较重要的知识点,或者踩过的坑。

为什么要深入理解栈?做C语言开发如果栈设置不合理或者使用不对,栈就会溢出,溢出就会遇到无法预测乱飞现象。所以对栈的深入理解是非常重要的。

版权声明:所有文章版权归嵌入式客栈所有,如商业使用,须嵌入式客栈授权。欢迎关注微信公众号,内容更丰富。
啥是栈

先来看一段动画:

栈操作动画演示https://www.zhihu.com/video/1235727858413977600

没有比这个更直观的啦,栈是一种受限的数据结构模型,其数据总是只能在顶部追加,利用一个指针进行索引,顶端叫栈顶,相对的一端底部称为栈底。栈是一种LIFO后入先出的数据结构。

栈就两种操作:

    PUSH,压栈,向栈内加入数据,
    POP,出栈

再进一步探讨:

首先将栈与堆分清,从看到这篇文章开始,我建议你不要把堆和栈连在一起叫,栈是栈,堆是堆,这是两回事,别混为一谈!(堆本文不深入讨论)

从C/C++编程语言的角度来看:

    相同点:都是一片内存区,在链接时指定栈区/堆区的位置以及大小。
    不同点:
        栈:由编译器分配,存放函数的参数值,局部变量,寄存器组(不同的单片机/处理器各有不同)、函数调用参数传递、中断异常产生时须保存处理器状态的寄存器值等
        堆:由程序员分配释放,对于C而言,malloc、realloc/free进行分配/释放,对C++而言,由new/delete分配/释放。

为啥用

栈这个数据模型的应用价值是什么呢?先来看一下单片机内部的可能有哪些栈应用?以STM32为例,参考IAR C/C++ Development Guide,P207

处理器模式建议段名描述SupervisorSVC_STACK操作系统栈IRQIRQ_STACK通用(IRQ)中断处理程序的堆栈。FIQFIQ_STACK用于高速(FIQ)中断处理程序的堆栈。UndefinedUND_STACK堆栈用于未定义的指令中断。 支持硬件协处理器和指令集扩展的软件仿真。AbortABT_STACK用于指令获取和数据访问存储器中止中断处理程序的堆栈。

如果使用RTOS还有任务栈,如果是Linux,其内核线程同样也需要栈的支持,等等这一切的一切栈,其本质上都是利用了栈数据模型的LIFO后入先出的特性,一个典型应用场景就是比如做一件事情做到一半而要转而去做另外一件事,对于芯片编程而言,就需要将当前的工作做个暂存,等另外一件事情做完了,再接着回来继续做。那么怎么做到呢,以一个中断处理为例,要记住当前的工作态有哪些信息需要暂存呢?PC指针,局部变量等就被压入栈,再将中断服务程序地址导入PC指针,进而去执行中断服务程序,待中断处理完毕,在将栈里的内容按照后入先出弹出到对应的寄存器就恢复了原程序的现场,进而继续执行。
怎么用

栈在哪里定义大小,定多大合适?这可能很多刚接触单片机开发的同学不是太清楚,下面就将比较常见的IAR开发环境为例如何定义栈定义栈大小的地方说明一下,这里以IAR8.4.1为例,有两种方式可以进行栈大小设置。
IDE设置

为了更加清楚明了,制作了一个GIF操作展示视频,在stack/heap中就可以设置了,其中stack用于设置栈区大小,heap用于设置堆大小。

这个demo中设置了其栈的大小为0x200,堆的大小为0x400,全编译后,检查map文件就印证了栈/堆的大小如预期所修改。
链接配置文件

其实对于比较熟悉的开发人员,上一种方式并非推荐用法。用链接配置文件将具有更好的灵活性,比如可以指定一个段的对齐方式,存储位置,某个符号的存储位置等等。这里同样为了直观也做了一个GIF动画,介绍如何通过链接文件进行栈/堆的大小配置。

其最终的效果也一样如预期将栈区的大小设置好了。
栈溢出

这里为了比较容易的展示栈溢出的问题,在main函数利用递归方法计算阶乘,代码如下:

    #include <stdio.h>
    #include "main.h"
    static uint32_t spSatte[200];
    static uint32_t spIndex = 0;
    /*为什么要用浮点数,因为数据非常大整型很快就会溢出*/
    float factorial(uint32_t n)
    {
        uint32_t sp = __get_MSP();    
        /*记录栈指针的变化情况*/
        spSatte[spIndex++] = sp;
        if(n==0 || n==1)
            return 1;
        else
            return (float)n*factorial(n-1);
    }
     
    int main(void)
    {
        float  x = 0;
        uint32_t  n = 20;
        printf("stack test:n");
        x = factorial(n);
        /*打印栈指针变化情况*/
        for(int i = 0;i<spIndex;i++)
            printf("MSP->%08Xn",spSatte[i]);
        
        /*打印阶乘结果*/
        printf("factorial(%d)=%fn",n,x);    
        while (1)
        {
        }
    }

为方便观察,将stm32f407xx_flash.icf 将栈改为256字节

    /*stm32f407xx_flash.icf 将栈改为256字节*/
    define symbol __ICFEDIT_size_cstack__ = 0x200;
    define symbol __ICFEDIT_size_heap__   = 0x200;

全编译后通过map文件来看下栈/堆的分配情况:

    "P2", part 3 of 3:                          0x400
      CSTACK                      0x2000'05d8   0x200  <Block>
        CSTACK           uninit   0x2000'05d8   0x200  <Block tail>
      HEAP                        0x2000'07d8   0x200  <Block>
        HEAP             uninit   0x2000'07d8   0x200  <Block tail>
                                - 0x2000'09d8   0x400

直观些,翻译成下图,CSTACK段分配在0x2000 05D8-0x2000 07D8,堆分配在0x2000 07D8-0x2000 08D8。

图为什么没有将0x2000 07D8画在栈区呢?通过调试发现,这个字空间没有用做栈的实际存储。将工程设置成simulation模式,debug进入main.o勾选掉,我们来计算20的阶乘,来具体看一下:

对这个动图解读一下:

    进入复位是,SP_main为0x200007D8,指向栈底,为空栈。那么这是怎么实现的呢?

    __vector_table                ;向量表
            DCD     sfe(CSTACK)   ;这条命令会将程序的CSTACK起始地址装载给SP_main
            DCD     Reset_Handler ; Reset Handler复位向量

    前面说0x200007D8并没有用到,怎么证明呢,在函数进入mian时,第一次压栈的情况如下:

    可见STM32栈的增长方向是向下增长的,也即顶在小地址端一侧
    栈存储元素是四字节对齐的,因为STM32的字长是字节,如果深入想想,如果不是司字节对齐会怎么样?留给感兴趣的思考一下。
    0x200007D8--0x200007DB 这个字存储单元并不是栈的有效存储空间。

栈的变化情况:

    stack test:
    MSP->200007A8
    MSP->20000790
    MSP->20000778
    MSP->20000760
    MSP->20000748
    MSP->20000730
    MSP->20000718
    MSP->20000700
    MSP->200006E8
    MSP->200006D0
    MSP->200006B8
    MSP->200006A0
    MSP->20000688
    MSP->20000670
    MSP->20000658
    MSP->20000640
    MSP->20000628
    MSP->20000610
    MSP->200005F8
    MSP->200005E0
    factorial(20)=2432902023163674771.785700 /*结算结果与用计算器一致*/

每调用一次阶乘函数,栈就压入4个字,由上面还可以看到第20次进入时,栈指针为0x200005E0,如果再压入4个字栈指针会变成0x200005C8,是这样吗,结果还对吗?将n改为21编译运行,来看一看:

看到了吧,惊喜来了,栈溢出了,程序已经不听话了,完全不知道在干嘛了。所以栈溢出的后果是极端危险的,完全无法预期,程序会带来什么后果。
总结一下

    栈是一种LIFO后入先出的数据结构模型,是C/C++程序运行时基础,没这个栈,C/C++玩不转
    栈在嵌入式编程领域随处可见,比如C栈,中断栈、异常栈、任务栈等等,但其基本工作原理都一样。支持两种基本数据操作:压栈、出栈。
    栈溢出程序的结果无法预期,所以合理的设置栈区大小是个永恒的话题,过大则浪费内存,过小则程序会飞。
    嵌入式编程递归函数要慎用,个人建议不用。比如IEC61508 功能安全标准中强行规定不可使用递归函数。
    STM32中__get_MSP可以得到当前栈指针的值,据此可以做一定程度的栈溢出保护措施。防止程序跑飞。
    通过上面递归调用测试,还可以得到一个启示,嵌入式编程函数嵌套的层级不宜过深,过深则需要相对较大的栈开销。
    .......
————————————————
版权声明:本文为CSDN博主「weixin_39664010」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_39664010/article/details/112339370

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c语言栈溢出的原因及解决办法_STM32编程:是时候深入理解栈了 的相关文章

  • 如何在 Cortex-M3 (STM32) 上从 RAM 执行函数?

    我正在尝试从 Cortex M3 处理器 STM32 上的 RAM 执行函数 该函数会擦除并重写内部闪存 所以我肯定需要在 RAM 中 但我该怎么做呢 我尝试过的是 使用 memcpy 将函数复制到 RAM 中的字节数组 检查它是否正确对齐
  • 处理器指令周期执行时间

    我的猜测是 no operation 内在 ARM 指令应花费 1 168 MHz 来执行 前提是每个NOP在一个时钟周期内执行 我想通过文档验证这一点 有关处理器指令周期执行时间的信息是否有标准位置 我试图确定 STM32f407IGh6
  • 当数据大小较小时,内存到内存 DMA 传输是否需要权衡?

    我正在学习 STM32 F4 微控制器 我正在尝试找出使用 DMA 的限制 根据我的理解和研究 我知道如果数据量较小 即设备使用DMA生成或消耗少量数据 则开销会增加 因为DMA传输需要DMA控制器执行操作 从而不必要地增加系统成本 我做了
  • 135-基于stm32单片机超声波非接触式感应水龙头控制系统Proteus仿真+源程序

    资料编号 135 一 功能介绍 1 采用stm32单片机 LCD1602显示屏 独立按键 DHT11传感器 电机 超声波传感器 制作一个基于stm32单片机超声波非接触式感应水龙头控制系统Proteus仿真 2 通过DHT11传感器检测当前
  • rt-thread studio中新建5.02版本报错

    先吐槽一下 rt thread studio出现BUG真多 好多时间都是在找BUG 但里面用好多控件还是挺好用的 真是又爱又恨 所以一般使用功能不多的话还是用keil多一点 创建5 02版本工程之后直接进行编译 直接会报下面这个错误 资源
  • STM32F4 通过软复位跳转到引导加载程序,无需 BOOT0 和 BOOT1 引脚

    我问这个问题是因为可以在这里找到类似问题的答案 通过应用程序跳转到 STM32 中的引导加载程序 即从用户闪存在引导模式下使用引导 0 和引导 1 引脚 用户 JF002 JF002回答 当我想跳转到引导加载程序时 我在其中一个备份寄存器中
  • STM32F103

    提示 来源正点原子 参考STM32F103 战舰开发指南V1 3PDF资料 文章目录 前言 一 pandas是什么 二 使用步骤 1 引入库 2 读入数据 总结 前言 提示 这里可以添加本文要记录的大概内容 开发环境硬件普中科技 接线图在g
  • STM32 GPIO工作原理详解

    STM32 GPIO介绍 1 STM32引脚说明 GPIO是通用输入 输出端口的简称 是STM32可控制的引脚 GPIO的引脚与外部硬件设备连接 可实现与外部通讯 控制外部硬件或者采集外部硬件数据的功能 以STM32F103ZET6芯片为例
  • SHT10温湿度传感器——STM32驱动

    实验效果 硬件外观 接线 3 3V供电 IIC通讯 代码获取 查看下方 END
  • 跟着野火学FreeRTOS:第一段(任务定义,切换以及临界段)

    在裸机系统中 系统的主体就是 C P U CPU CP U 按照预先设定的程序逻辑在 m a i n
  • 最终启动顺序错误 - STM32L476 的 Eclipse System Workbench 调试

    我正在尝试调试和运行 STM32L476 的简单汇编代码 我已经设置了 Eclipse Oxygen 在 Eclipse 中安装了最新版本的 System Workbench 插件并安装了 ST Link 驱动程序 IDE 成功构建了程序
  • 串口通讯第一次发送数据多了一字节

    先初始化IO再初始化串口 导致第一次发送时 多出一个字节数据 优化方案 先初始化串口再初始化IO 即可正常通讯
  • 1.69寸SPI接口240*280TFT液晶显示模块使用中碰到的问题

    1 69寸SPI接口240 280TFT液晶显示模块使用中碰到的问题说明并记录一下 在网上买了1 69寸液晶显示模块 使用spi接口 分辨率240 280 给的参考程序是GPIO模拟的SPI接口 打算先移植到FreeRtos测试 再慢慢使用
  • 1.69寸SPI接口240*280TFT液晶显示模块使用中碰到的问题

    1 69寸SPI接口240 280TFT液晶显示模块使用中碰到的问题说明并记录一下 在网上买了1 69寸液晶显示模块 使用spi接口 分辨率240 280 给的参考程序是GPIO模拟的SPI接口 打算先移植到FreeRtos测试 再慢慢使用
  • STM32F4XX的12位ADC采集数值超过4096&右对齐模式设置失败

    文章目录 一 前言 二 问题1 数值超过4096 三 问题1的排错过程 四 问题2 右对齐模式设置失败 五 问题2的解决方法 5 1 将ADC ExternalTrigConv设置为0 5 2 使用ADC StructInit 函数 一 前
  • for循环延时时间计算

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 pandas是什么 二 使用步骤 1 引入库 2 读入数据 总结 前言 之前做led点亮的实验 好像是被delay函数影响了 因为delay参数设置的不对
  • 库函数点亮Led

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 pandas是什么 二 使用步骤 1 引入库 2 读入数据 总结 前言 提示 这里可以添加本文要记录的大概内容 例如 随着人工智能的不断发展 机器学习这门
  • Cortex-M3与M4权威指南

    处理器类型 所有的ARM Cortex M 处理器是32位的精简指令集处理器 它们有 32位寄存器 32位内部数据路径 32位总线接口 除了32位数据 Cortex M处理器也可以有效地处理器8位和16位数据以及支持许多涉及64位数据的操作
  • STM32 上的位置无关代码 - 指针

    我已成功在 STM32 上构建并运行位置无关的代码 向量表和 GOT 已修补 一切正常 但我对这样的代码有问题 double myAdd double x return x 0 1 double ptrmyAdd double myAdd
  • HAL_Delay() 陷入无限循环

    我被 HAL Delay 函数困住了 当我调用此函数 HAL Delay 时 控制陷入无限循环 在寻找问题的过程中 我发现了这个 http www openstm32 org forumthread2145 threadId2146 htt

随机推荐

  • 嵌入式MCU工程师毕业1年,接下来要学的东西有:

    刚毕业 nbsp 1 nbsp 年多了 接下来感觉有好多东西要学习 一 单片机方面的 比如 COSii和 COSiii 还有FreeRTOS等微型操作系统 除了操作系统之外 还要学习诸如emwin界面设计 还想搞一下Wifi 以太网 蓝牙B
  • RT-THREAD 线程同步与通讯:信号量、互斥量、事件、邮箱、队列、信号

    线程同步包括 xff1a 信号量 互斥量 事件 线程通讯包括 xff1a 邮箱 队列 信号 rt thread源文件说明 xff1a ipc c xff1a 信号量 xff08 sem xff09 互斥信号 xff08 mutex xff0
  • easyflash 教程

    可以看easyflash下的docs文档 xff0c 万一你们手头没有文档呢 这里我就直接黏贴了 API 说明文档 xff1a docs zh api md 通用移植文档 xff1a docs zh port md EasyFlash AP
  • 微秒(us)延时 程序

    微秒级的延时最好用systick 1 来计算 使用方法3 xff08 wait loop index xff09 时间变动会比较大 函数10us100us500us900usus delay111 2101 2501 3901 2us de
  • 发送一个字节数据要花多少时间,串口每秒可以发送多少数据

    以波特率250000为力 1s 250 000 61 4us 不是很严谨的以下图反推 xff0c 一个bit的时间正好就是4usec 波特率的单位应该就是比特每秒bit s bsp不好说明到底是bit 还是 byte 每个字节包含11个bi
  • lwip组播实现和原理-STM32F407

    实现 在lwipopts h中定义LWIP IGMP define LWIP IGMP 1LWIP初始化添加进入组播代码 span class token class name err t span err span class token
  • RTT WK2412 spi-uart

    1 添加软件包 xff0c 打开硬件 2 代码里根据硬件配置spi span class token macro property span class token directive hash span span class token
  • gazebo导入sdf模型

    模型文件 模型文件结构 xff1a simple car model config model sdf model config span class token operator lt span span class token oper
  • MOS管的<控制电路>与<防反接电路>

    为了方便记忆 xff0c 我不管D与S xff0c 只说MOS管中的二极管方向 另外G是控制端 这是一篇只管结果的文章 xff0c 大家只要记住就行 懂原理vs记结果 懂原理以分析一切现象 xff0c 但每次使用都要分析一次 xff1b 记
  • rt-thread studio 开启easyflash(env)

    文章目录 前言一 启用外部norflash补充说明 前言 提示 xff1a 这里可以添加本文要记录的大概内容 xff1a 环境 xff1a Art pi开发板 bsp版本1 2 1 RT Thread 4 0 3 否则添加不了fal软件包
  • 常见开源物联网平台

    下面是我用过的 JetLink 重庆 ThingsPanel 国内 ThingsBoard 国外
  • 串口工具 和 终端工具的区别 -个人猜测

    总体来说 xff0c 都是人机交互的工具 xff0c 和图形控制软件并列 可以叫命令行工具 xff1f 串口工具 显示 输入分屏 xff1a 接收和发送分开 xff0c 输入输出相互独立 输入是文本框 xff0c 点击发送时才发送 记录lo
  • RT-Thread 添加设备初始化的方式-- INIT_BOARD_EXPORT(fn)

    代码用的是rtthread simulator v0 1 0 最开始不知道rt thread在哪里给硬件初始化 xff0c 或者在哪里添加新硬件的初始化函数 例如要添加GPIO的初始化 xff0c 和操作 1 关键的就是INIT BOARD
  • Visual Studio c#生成exe可执行文件

    生成exe可执行文件方式 1 调试完毕 xff0c 确认程序无误后 xff1a 生成 生成解决方案 2 程序所在文件夹 文件名Inverter Ver0102 2019 09 20 debug Inverter bin x64 Debug
  • 在emwin中,调用GUI_Delay()函数,程序卡死,黑屏

    没有OS TimeMS 43 1 可以在systick里或者定时器里 定时 43 1
  • STM32 堆栈大小的设置及分析

    一 通过map文件了解堆栈分配 STM32 MDK5 避免堆栈溢出 环境 xff1a STM32F103C8T6 MDK5 在最近的一个项目的开发中 xff0c 每当调用到一个函数 xff0c 程序就直接跑飞 debug跟进去看不出什么逻辑
  • C# VS2017中Windows窗体更改图标

    一 图片准备 1 需要 ICO格式的文件 2 矢量图下载可在阿里巴巴的矢量图库中下载 xff08 https www iconfont cn xff09 3 下载PNF文件的图片后需转成 ICO格式 xff08 https www uupo
  • 关于RS485的DMA发送,以及EN使能脚的自动切换

    一 电路设计 1 低成本非隔离 xff1a 3 3v系统同样 xff0c 将5V改为3 3即可 同时采用TX连接三极管 xff0c 实现三极管驱动RS485芯片的EN使能脚 xff0c 从而省下一个IO口控制 隔离只需要将两个信号线用光耦隔
  • 百度2014校园招聘笔试题(武汉站 9.28)

    一 简答题 xff08 本题共30分 xff09 动态链接库与静态链接库分别有什么优缺点 xff1f xff08 10分 xff09 轮训任务调度和抢占式任务调度有什么区别 xff1f xff08 10分 xff09 请列出数据库中常用的锁
  • c语言栈溢出的原因及解决办法_STM32编程:是时候深入理解栈了

    导读 从这篇文章开始 xff0c 将会不定期更新关于嵌入式C语言编程相关的个人认为比较重要的知识点 xff0c 或者踩过的坑 为什么要深入理解栈 xff1f 做C语言开发如果栈设置不合理或者使用不对 xff0c 栈就会溢出 xff0c 溢出