0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

栈是一种LIFO后入先出的什么数据结构?

lhl545545 来源:玩转单片机 作者:玩转单片机 2020-06-09 09:26 次阅读

没有比这个更直观的啦,栈是一种受限的数据结构模型,其数据总是只能在顶部追加,利用一个指针进行索引,顶端叫栈顶,相对的一端底部称为栈底。栈是一种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:

x = factorial(n);

/*打印栈指针变化情况*/

for(int i = 0;i《spIndex;i++)

printf(“MSP-》%08X

spSatte[i]);

/*打印阶乘结果*/

printf(“factorial(%d)=%f

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 0280-0x2000 0480,堆分配在0x2000 0480-0x2000 0680。

栈是一种LIFO后入先出的什么数据结构?

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

栈是一种LIFO后入先出的什么数据结构?

对这个动图解读一下:

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

__vector_table ;向量表

DCD sfe(CSTACK) ;这条命令会将程序的CSTACK起始地址装载给SP_main

DCD Reset_Handler ; Reset Handler复位向量

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

栈是一种LIFO后入先出的什么数据结构?

可见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可以得到当前栈指针的值,据此可以做一定程度的栈溢出保护措施。防止程序跑飞。

通过上面递归调用测试,还可以得到一个启示,嵌入式编程函数嵌套的层级不宜过深,过深则需要相对较大的栈开销。
责任编辑:pj

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 编译器
    +关注

    关注

    1

    文章

    1564

    浏览量

    48531
  • 数据结构
    +关注

    关注

    3

    文章

    557

    浏览量

    39872
收藏 人收藏

    评论

    相关推荐

    C语言数据结构之跳表详解

    大家好,今天分享一篇C语言数据结构相关的文章--跳表。
    的头像 发表于 12-29 09:32 488次阅读
    C语言<b class='flag-5'>数据结构</b>之跳表详解

    redis数据结构的底层实现

    Redis是一种内存键值数据库,常用于缓存、消息队列、实时数据分析等场景。它的高性能得益于其精心设计的数据结构和底层实现。本文将详细介绍Redis常用的
    的头像 发表于 12-05 10:14 284次阅读

    不同数据结构的定义代码

    数据结构是相互之间存在一种或多种特定关系的数据元素的集合。
    的头像 发表于 11-29 14:13 315次阅读

    redis的五种数据类型底层数据结构

    Redis是一种内存数据存储系统,支持多种数据结构。这些数据结构不仅可以满足常见的存储需求,还能够通过其底层数据结构提供高效的操作和查询。以
    的头像 发表于 11-16 11:18 323次阅读

    epoll的基础数据结构

    一、epoll的基础数据结构 在开始研究源代码之前,我们先看一下 epoll 中使用的数据结构,分别是 eventpoll、epitem 和 eppoll_entry。 1、eventpoll 我们
    的头像 发表于 11-10 10:20 276次阅读
    epoll的基础<b class='flag-5'>数据结构</b>

    NetApp的数据结构是如何演变的

    混合和多云部署模型是企业IT组织的新常态。随着这些复杂的环境,围绕数据管理的新挑战出现了。NetApp的数据管理愿景是一种无缝连接不同的数据结构云,无论它们是私有环境、公共环境还是混合
    发表于 08-25 17:15 0次下载
    NetApp的<b class='flag-5'>数据结构</b>是如何演变的

    队列与C++中的queue详解

    队列就是一种线性的数据结构,它与日常生活中排队的队列相似,即先进先出LIFO, First In First Out),这点也是它与栈(Stack)的最大不同之处。
    的头像 发表于 07-18 17:31 723次阅读
    队列与C++中的queue详解

    快速介绍8种常用数据结构

    数据结构一种特殊的组织和存储数据的方式,可以使我们可以更高效地对存储的数据执行操作。数据结构在计算机科学和软件工程领域具有广泛而多样的用途
    发表于 06-21 09:27 527次阅读
    快速介绍8种常用<b class='flag-5'>数据结构</b>

    Linux中的进程栈、线程栈、内核栈以及中断栈

    首先,栈 (stack) 是一种串列形式的 数据结构。这种数据结构的特点是 后入先出 (LIFO, Last In First Out),
    的头像 发表于 05-14 09:30 479次阅读
    Linux中的进程栈、线程栈、内核栈以及中断栈

    嵌入式软件常见的8种数据结构

    数据结构一种特殊的组织和存储数据的方式,可以使我们可以更高效地对存储的数据执行操作。数据结构在计算机科学和软件工程领域具有广泛而多样的用途
    的头像 发表于 05-04 10:28 597次阅读
    嵌入式软件常见的8种<b class='flag-5'>数据结构</b>

    数据结构解决滑动窗口问题

    前文用 [单调栈解决三道算法问题]介绍了单调栈这种特殊数据结构,本文写一个类似的数据结构「单调队列」。 也许这种数据结构的名字你没听过,其实没啥难的,就是一个「队列」,只是使用了一点巧妙的方法,使得 队列中的元素全都是单调递增
    的头像 发表于 04-19 10:50 430次阅读
    <b class='flag-5'>数据结构</b>解决滑动窗口问题

    常见的数据结构有哪些

    数据结构是计算机存储、组织数据的方式,是指相互之间存在一种或多种特定关系的数据元素的集合
    的头像 发表于 04-06 17:26 2153次阅读
    常见的<b class='flag-5'>数据结构</b>有哪些

    算法和数据结构基础知识分享(下)

    有哪些常见的数据结构?基本操作是什么?常见的排序算法是如何实现的?各有什么优缺点?本文简要分享算法基础、常见的数据结构以及排序算法。
    的头像 发表于 04-06 16:48 530次阅读
    算法和<b class='flag-5'>数据结构</b>基础知识分享(下)

    算法和数据结构基础知识分享(中)

    有哪些常见的数据结构?基本操作是什么?常见的排序算法是如何实现的?各有什么优缺点?本文简要分享算法基础、常见的数据结构以及排序算法。
    的头像 发表于 04-06 16:48 373次阅读
    算法和<b class='flag-5'>数据结构</b>基础知识分享(中)

    算法和数据结构基础知识分享(上)

    有哪些常见的数据结构?基本操作是什么?常见的排序算法是如何实现的?各有什么优缺点?本文简要分享算法基础、常见的数据结构以及排序算法。
    的头像 发表于 04-06 16:48 558次阅读
    算法和<b class='flag-5'>数据结构</b>基础知识分享(上)