打印
[经验分享]

一个故事看懂单片机中的堆栈

[复制链接]
1622|22
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wilhelmina2|  楼主 | 2023-9-17 23:00 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在关于单片机的众多让你头晕脑胀、摸不着头脑甚至想撞墙的概念里面,“堆栈”可能是其中最可恶的一个,因为即使单单是从汉语的角度来理解这个词就已经让你很晕了。
其实我也想不通这是哪位大侠的创意,不过不用担心,这里我们完全不去讨论关于这个词的问题(这个词用得其实很好“堆”和“栈”都有他们各自的意思,准确的概括了这个区域的功能,有兴趣可以Baidu一下)。
这里我会打一个比较有趣的比方,以此来绕过那些令你想撞墙的概念,并使你在直觉上对“堆栈”这个概念有一个深刻的理解。
你基本上应该清楚,单片机里面是有存储区和CPU的,如果你不清楚,那么我刚刚告诉你了,请记住。现在,请你把单片中的CPU想成一个人(你完全可以把他想成是你宿舍的那个天天和你吵嘴的同学,一会你就会发现这会非常有趣)。
在这里就叫他C哥吧,不过这个人不同于常人,有一些特点,一会我们会慢慢说清楚,现在要告诉你的关于这个人的第一个特点是:他的**能力很差。
下面,请你把存储区想象成一个一个排好的小盒子,这些盒子的作用大致可以分成两类:
1、保存写有你命令的纸条,比如你在某个盒子里面的纸条上写着:去洗我的袜子!;
2、保存你的一些东西,比如你那双正在污染宿舍空气的臭袜子。因为C哥是一个**力不怎么好的人,所以,这些盒子都有自己的编号,以方便他查找。
那么,现在,我们可以来说明一下单片机是如何工作的了。
首先,你要把所有的命令还有需要处理的东西放进那些小盒子,比如刚才提到的你那双待洗的袜子还有那张纸条,这时你应该发现C哥另一个特点:笨——他只会做你明确告诉他的事情,也就是说,如果你没有在纸条上写“去洗我的袜子!”,那么C哥极有可能会无动于衷地看着你的袜子直到他被熏晕倒,当然,更可能的情况是他根本找不到你的袜子…
好了,当你把要做的事情和该怎么做写到盒子里之后,下面的任务就交给C哥了。
C哥做事真的很讲原则,他会按照你给定的顺序或者——如果你没有给定的话,根据盒子上面的编号按照从小到大的顺序——一个一个地打开盒子,读取里面的命令、处理相应的事件,直到所有的事情都执行完毕,他就会休息。
请你牢记这个简单而有趣的过程,因为其实单片机就是这样工作的,当然,这里忽略了许多细节,但是这对你从直觉上理解单片机的概念以及足够了。
下面,就要开始说明堆栈这个概念了,思来想去,还是觉得如果直接把“堆栈”这个词用到文中来,实在不符合本文的风格,考虑到其实“堆栈”也是存贮区(这一点你要记住,堆栈并不是一个像专用寄存器那样专门的一个区域,它是由你在通用RAM区指定的。)
按照本文的说法也就是一些盒子,所以,现在我们把“堆栈”改名叫“**盒子”,你可以感觉到,“堆栈”的作用和**有极大的关系,不过你也不用在这里纠结这个名字的由来,下面我会说的。
现在,请注意,我要开始解释“**盒子”了,也就是“堆栈”。
大致上说,“**盒子”的作用是当C哥执行某任务到一半的时候突然有了更紧急的是事情要执行的时候用来保存当前任务的(包括盒子的编号和盒子里面的东西)。
这么说你肯定晕了,其实,通俗一点,就是当C哥洗袜子洗到一半的时候突然接到你的命令要去打开另一个盒子(那个盒子里的纸条上可能写着“给我换尿布”)并执行里面的命令,因为C哥**力很差,以至于他做完那件紧急的事情后记不起要回到哪个盒子来继续执行“洗袜子”这个命令。
这时候,他要把现在手头的东西保存到“**盒子”里,要保存的东西有:
1、放着纸条和袜子的盒子的编号(注意这里其实是两项内容);
2、那双袜子。
这样,当他执行完紧急任务后会去**盒子里,从里面找到两张纸条,和一双袜子(这个时候C哥还是没有想起来他要洗袜子,他必须要到那张写着洗袜子命令的纸条),他按照两张纸条的信息知道自己要去哪个盒子去洗袜子,并在那里继续完成洗袜子的任务。
你可能会发现,在这一段的解释里面有一个重要的漏洞,那就是在C哥执行完紧急任务后他是如何知道储存着原来的任务信息的盒子的编号是存储在哪个“**盒子”里呢?别着急,下面我会解释的。
从本质来说,“**盒子”与普通的盒子是没有区别的,他们都是单片机里面的存储单元,证明这一点的最好证据就是堆栈是需要你来指定的,也就是说,你要预先把一些盒子指定为“**盒子”。
下面,说明一下是如何指定“**盒子”的。
其实这个过程很简单,在单片机的专用寄存器里面有一个SP指针(81H),这个指针里面记录着堆栈的开始处的地址。
用符合本文的话来解释就是,C哥的衣服上有一个口袋(也就是SP指针),这个口袋里面的“神奇纸条”上记录着第一个“**盒子”的编号,而指定“**盒子”的过程就是你在这张“神奇纸条”上写上一个盒子的编号(作为第一个“**盒子”的编号),这个纸条会自动地将纸条上的编号加1或者减1。
所以,某个目前并不确定的区域内盒子具备了成为“**盒子”的可能,注意,堆栈的大小是不能规定的,这就是为什么用“生长”这个词来形容堆栈。
现在,关于堆栈的概念基本上都介绍完了,但是,我知道,你可能还是很晕,甚至比看之前还晕,那是因为刚才叙述的这个过程是分开的,而且逻辑上并不是顺序的,下面,顺序的说一下,相信你马上就明白了。
主角仍然是傻傻笨笨但任劳任怨的C哥,他一个一个的打开盒子按照里面的纸条上的说明执行你规定的任务。
而你,为了防止他在执行复杂任务时犯傻,把一个盒子指定为“**盒子”,并把这个“**盒子”的位置写在了一张 “神奇纸条”上放在了C哥的口袋里。
现在,C哥正在洗你的袜子,这个时候,他突然接到你的命令要去给你换尿布,而C哥知道自己很笨,所以他自动地掏出了口袋里的纸条,找到了第一个“**盒子”,然后拿出一张空白纸条,把装着“给我洗袜子”那张纸条的盒子的编号写在了上面并放进“**盒子”。
然后,他把“神奇纸条”放回了口袋里。当这个任务完成后“神奇纸条”会自动将写在它上面的编号加1,也就是将一个新的、空的“**盒子”的编号写在上面。
之后,他会按照刚才的过程把装着袜子的那个盒子的编号以及袜子本身分别放进不同的**盒子(现在已经有三个盒子成为“**盒子”,堆栈已经长大了,红色下划线的字体就是这三个盒子里的内容,注意是有先后顺序的)。
再然后,他就去给你换尿布了…
现在,尿布换完了,不过,果不其然,C哥完全忘记了他要给你洗袜子这件事情了,不过,他记得一件事,那就是看口袋里的纸条。
于是,他摸出了口袋里的纸条,上面当然是一个“**盒子”的编号,他按照编号找到了第一个“**盒子”(按照上一段的顺序应该是第三个“**盒子”),里面应该是一双你的袜子,于是他拿到了你的袜子。
但是,他还是不知道该干什么,于是他再次摸出了“神奇纸条”,这时,纸条上的编号已经自动减1了,于是,他找到了新的“**盒子”,里面的纸条上记录着袜子本来放置的盒子的编号,于是,他把袜子放到了那个盒子里。
恩,你可以想到,现在C哥还是不知道要对袜子做些什么,他耐心的又一次摸出了那张“神奇纸条”,这次按照上面的编号,他找到了一张纸条,上面写着的仍然是一个盒子的编号。
C哥按照编号找到了那个盒子,发现那个盒子里的纸条上写着“给我洗袜子!”…至此,C哥又回到了原来的任务——洗袜子。
现在,我**你已经明白了,堆栈其实就是你指定的一个些存储单元,这些存储单元被指定只用来保存一些特殊信息,比如地址(保护断点)或者一些数据(保护现场)。
如果你一定要说这个存储区有什么特别的话,那就是:
1、这些存储单元内的内容都是CPU在执行某任务中途被打断时的一些相关参数;
2、这些存储单元的地址被记在了一个叫堆栈指针的地方,也就是C哥口袋里的那张纸条上!

使用特权

评论回复
沙发
uiint| | 2023-10-6 20:50 | 只看该作者
堆栈通常位于RAM(随机访问存储器)中,可以由程序员指定。堆栈的大小可以根据实际需要进行设置,但通常不会超过单片机的RAM容量。

使用特权

评论回复
板凳
maqianqu| | 2023-10-6 21:29 | 只看该作者
堆栈的工作原理是基于后进先出(LIFO)的原则,即最后进入堆栈的内容会被首先取出。这一点与常用的数据结构栈相似。

使用特权

评论回复
地板
updownq| | 2023-10-7 21:45 | 只看该作者
当一个函数被调用时,其局部变量和参数会被压入堆栈。函数执行完毕后,这些数据会从堆栈中弹出,以恢复调用函数前的状态。这有助于实现函数的递归调用和多层嵌套调用。

使用特权

评论回复
5
cemaj| | 2023-10-8 18:02 | 只看该作者
在单片机中,堆栈的使用需要注意以下几点:

堆栈的指针(SP)必须始终指向堆栈的顶部,以便正确地存取堆栈中的数据。
堆栈的大小必须足够大,以存储所有需要存储的数据。
堆栈的数据必须按照正确的顺序存储,即后进先出。
堆栈的使用必须遵循一定的规则,以避免数据丢失或溢出。

使用特权

评论回复
6
hudi008| | 2023-10-8 18:11 | 只看该作者
在单片机中,堆栈通常用于函数调用、中断处理等操作。当一个函数被调用时,它的参数和局部变量会被压入堆栈中。当函数返回时,这些数据会被从堆栈中弹出。类似地,当一个中断被处理时,中断服务程序会被压入堆栈中,然后返回到被中断的程序。

使用特权

评论回复
7
sanfuzi| | 2023-10-8 22:36 | 只看该作者
单片机中的堆栈是一种特定的存储区或寄存器,它的一端是固定的,另一端是浮动的。堆这个存储区存入的数据,是一种特殊的数据结构。所有的数据存入或取出,只能在浮动的一端(称栈顶)进行,严格按照“先进后出”的原则存取,位于其中间的元素,必须在其栈上部(后进栈者)诸元素逐个移出后才能取出。

使用特权

评论回复
8
单片小菜| | 2023-10-10 10:02 | 只看该作者
楼主仔细排版就好了,这样看太累了。

使用特权

评论回复
9
louliana| | 2023-10-11 22:28 | 只看该作者
单片机中的堆栈是一种数据结构,用于存储程序运行时所需的数据和指令。堆栈通常由两个主要元素组成:栈顶指针和栈底指针。栈顶指针指向当前栈中栈顶元素的地址,而栈底指针指向当前栈中栈底元素的地址。

使用特权

评论回复
10
ingramward| | 2023-10-11 22:54 | 只看该作者
堆栈是一种特殊的存储区,用于临时存储数据。堆栈遵循后进先出(LIFO)的原则,即最后进入堆栈的数据最先被取出。

使用特权

评论回复
11
tpgf| | 2023-10-12 14:02 | 只看该作者
栈区由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈

使用特权

评论回复
12
八层楼| | 2023-10-12 14:32 | 只看该作者
  堆和栈,一般堆是由低地址往上增长,栈是由往下减少

使用特权

评论回复
13
guanjiaer| | 2023-10-12 15:57 | 只看该作者
全局变量和静态变量储存的位置,和堆栈无关

使用特权

评论回复
14
keaibukelian| | 2023-10-12 16:16 | 只看该作者
如果我们设置了堆的空间大小,但是我们程序中没有进行malloc申请,那么在程序事假运行的时候,我们栈的空间超过本身设置的空间,进入到堆里面,那么程序是不会出错的,但是超过了堆的空间了,进入到全局变量区域,就会出现莫名其妙的错误

使用特权

评论回复
15
abotomson| | 2023-10-12 19:52 | 只看该作者
在单片机的汇编代码中,堆栈的操作主要通过SAVE和RESTORE指令来完成。SAVE指令将当前PC值压入堆栈,RESTORE指令则从堆栈中弹出PC值。

使用特权

评论回复
16
观海| | 2023-10-12 20:12 | 只看该作者
C语言不提供内存保护机制类似的功能,如果堆一直增长,栈一直申请,然后就会导致栈溢出,从而导致程序崩溃

使用特权

评论回复
17
uiint| | 2023-10-12 20:17 | 只看该作者
堆栈的操作是有一定顺序的,即后入先出(LIFO)。因此,在单片机中,使用堆栈时需要注意避免溢出和下溢的情况,即不要在栈顶和栈底之间移动数据,以免造成不可预知的结果。

使用特权

评论回复
18
wwppd| | 2023-10-12 20:38 | 只看该作者
堆栈(Stack)是计算机科学中的一个重要概念,也是单片机中常用的数据结构和编程方法。堆栈用于存储数据和程序中的临时变量,同时也用于在函数调用和返回时保存和恢复程序的状态。

使用特权

评论回复
19
jkl21| | 2023-10-12 21:46 | 只看该作者
单片机中,堆栈是一个特殊的存储区域,主要用于临时存放数据和地址。

使用特权

评论回复
20
heimaojingzhang| | 2023-10-12 22:33 | 只看该作者
堆不属于程序,堆是独立的,是公用的

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

24

主题

1301

帖子

1

粉丝