深入浅出地讲解 (深入浅出的讲解知识)

堆栈是个啥?

这个事情要从打工人CPU说起。CPU内部有个核心模块叫ALU,专门用来做逻辑运算和数学计算。数学计算:比如1+1,我们人类算数学题的时候,如果算式比较复杂,没法一次性算出结果,会先把这两个数相加,得到临时结果,再把临时结果和剩下的数相加。同理,在算式复杂的时候,ALU也是这么做的。这就需要把临时结果找个地方存放一下。

于是攻城狮们在CPU里又设计了一些可以存放数字的模块叫寄存器。它们的名字分别叫AX、BX、CX、DX等。没错儿,在如此高大上的CPU内部起名就是这么随意。有了寄存器,ALU就可以把临时结果保存在寄存器中,需要的时候再拿出来用。这就解决了复杂算式分步计算的问题。

深入浅出式的讲解,堆栈的操作规则

为了实现更复杂的计算,能不能做很多寄存器呢?答案是不能。复杂性、成本。这会增加CPU设计的复杂性以及成本,于是只能从外部请帮手了。

大家想想,这个帮手需要什么样的条件?最关键的一点是:读写速度要快,毕竟CPU可是个急性子,按照这个条件招标,最后中标的是内存,而硬盘和软盘、光盘都因为速度太慢成了陪跑的。

(二)栈的概念。接下来,要在内存中划出一片专门的区域,用来临时存储数据。既然是专用的,就得有个名字,叫做:栈。注意,内存里还有个区域叫堆,和栈的特性很不一样。有(求)机(三)会(连),以后有机会再和大家聊。

深入浅出式的讲解,堆栈的操作规则

所以说,栈在本质上只是内存中的一片区域,它的特殊之处在于CPU从中存取数据的方式。大家都见过给弹匣装*弹子**,把*弹子**一个个往里压,最先压进去的,处于弹匣的最里面。在射击的时候,这颗*弹子**会最后一个被打出去。

简而言之就是:先入后出,相应的:后入先出。规则非常的简单。

CPU一看,这个东西好。很多时候对数据的操作都有这种先入后出的特点,于是CPU一拍大腿,决定对堆栈的操作也遵循弹匣操作的规律。下面一分钟的动画,做了2个小时。我画个图方便大家理解(示意图,细节做了简化)。这是内存,上面是一个个的存储单元。这一片就用来做堆栈。CPU把堆栈看成一个弹匣,把数据当成*弹子**。这个位置(高地址)叫栈底,固定不动。这个位置(低地址)叫栈顶,是可以变化的。在堆栈里没有数据的时候,栈顶和栈底是重合的。当需要往栈里存数据的时候,就把栈顶的位置往上(即低地址方向)挪一挪,腾出一个位置来,然后把数据放进去。这种动作叫做压栈或入栈,再来个数据,再压栈,以此类推。当需要从栈里取出数据的时候,就先把数据复制到CPU中的寄存器,然后把栈顶的位置往下挪一挪。这种动作叫做弹栈或出栈。注意,出栈后,这个数据还在堆栈里,但是已经被当做垃圾了。有人说我想绕过2233这个数据,直接取AA55这个数据行不行,答案是不行。不阔以。前面说了:堆栈里的数据,先入的必须后出。上面讲了堆栈操作的原理。(三)堆栈和函数调用。下面聊聊堆栈在编程中最常见的应用。函数调用。想学好编程语言的,这个部分绝对不能错过。程序在运行的时候,是以机器码的形式躺平在内存里面的。每一句机器码都有自己的位置,称为地址。CPU执行程序的过程,就是把程序中的每一句机器码挨个儿拿过来。分析出这句机器码是干啥的,然后做相应的操作。该取数据的取数据,该跳转的跳转,该死机的死机(误)。而所谓函数就是一堆代码的打包。函数调用,就是CPU在执行一个函数中的代码时,临时蹦跶到另一个函数,执行另一个函数里的代码。执行完了再蹦跶回来。用专业术语说,叫做函数跳转和返回。大家看,从这里跳转到另一个函数,待会返回后,接下来要执行哪句代码呢?是这句,所以在跳转的时候,CPU得把这句代码的地址找个地方记下来,不然待会返回时就不知道执行哪一句了。毕竟CPU太忙了、也很健忘。为了记录这个地址,就要用到堆栈。在跳转时,CPU把这个地址压栈,在返回时,把这个位置弹栈,地址就会被复制给了CPU,CPU就会依据这个地址,跑到这继续执行代码。

深入浅出式的讲解,堆栈的操作规则

有人可能要问了,记录位置这种破事,随便找一块内存区域都行,为啥非要用堆栈呢?这是因为,实际的程序中,函数调用都是嵌套的套娃套娃套娃….这就需要把很多个返回地址按顺序保存,然后再按相反的顺序一个个提取出来。这种先入后出的数据操作,当然是用堆栈最合适。我有一个大胆的想法。这时候,有些调皮的同学可能要问了,假如函数无限调用下去而从来不返回,会怎么样呢?

深入浅出式的讲解,堆栈的操作规则

从上面的例子可以看出,每一次函数调用都会消耗堆栈的一些空间,而整个堆栈的大小是有限(固定大小)的。如果函数不停的调用而不返回,返回地址D,堆栈就被塞满了。这个时候再试图把数据压栈就会导致错误,叫做堆栈溢出,程序就崩溃了。

深入浅出式的讲解,堆栈的操作规则

给大家演示一下,写一个函数A(),然后在A的代码里调用它自己。在程序入口处直接调用A函数运行,瞬间就崩了。

深入浅出式的讲解,堆栈的操作规则

多说一句,如果一个函数确实需要调用自己,那么必须设定一个限定条件,避免这种调用无限的继续下去。这就是所谓的递归调用了。

深入浅出式的讲解,堆栈的操作规则