细致分析C中栈的使用(适合初学者)

关于C中函数调用时栈的具体变化

前言:

一些简单的堆与栈的区域划分这里请自行了解不做赘述。今天深度剖析在函数调用时发生了什么。具体栈的变化情况以及SP与FP的关系与它们的作用。我们都知道栈简单来说是保存和恢复上下文的。但是其中的每个细节,哪些会先入栈,哪些不入栈,什么是栈的销毁?以及讨论一些编译器中有意思的现象。

首先明白堆栈不是一回事,没有堆栈,堆是堆,栈是栈。

四种栈:

网上很多介绍,总的来说就是SP指向内容是否为空(是否满栈),以及栈的增长方向的组合(增减栈),一共四种。这会影响数据出栈入栈时的具体实现(先动SP还是先写入数据),这个地方是很合理的,稍微考虑就会明白。主要是打破我们对栈的认识,它是一种数据结构,有着自己的多样性,不要一想着栈就是向下增长的,就是很模糊的一个进入的概念,更应该把握它的细节。

写一个两级嵌套的函数调用的程序:

 

 

                

 

 

这里我直接总结,无论在x86还是la下,大体的流程都是一样的。进去后先移动sp到栈顶(低地址),注意这里不是我们想象中便运行边入栈,而是此时这里编译器应该已经知道我们在这层函数调用会有多少开销(之前一直都是错误的以为),会把sp放在栈顶,然后将ra和fp入栈。然后将fp指向原来sp的位置,也就是栈底(底是相对的底部)。然后根据fp加上偏移的方式把局部变量入栈。然后发生函数调用时时,把栈中数据读出来传递给默认的传参寄存器(这里la的参数寄存器比较多所以可以都使用寄存器),跳入目标函数,目标函数再重复上述操作,入栈fp,ra,把寄存器中的参数入栈.......,返回的时候将fp,ra出栈使用,ra用来返回上一级函数结束后的下一条指令地址,fp指向上一级fp(一进入函数时储存下来的),有点链表的感觉。当寄存器不够时,还会使用栈储存传递参数,但是这时是一个引用的概念,即把原来栈地址中的值写入新的栈地址,新的栈地址供跳入函数使用,所以我们一个函数中,你去改是形式参数的值,并不会真正改变原来调用前变量的值。因为这个你改的是新栈地址中的内容,而不是原来的地址中的内容,所以swap这样的函数,必须用到原来数据的地址去操作,而不是仅仅做值的传递。

补充:

 

其实我们可以看见,在函数返回时,并未对任何使用过的栈空间进行处理,仅仅把SP,FP做了移动。我们经常听到说当函数结束时,局部变量摧毁或者释放,之前我一直认为编译器会把这段空间清零或者是设置为非法地址之类的,但是经过亲自实验,加上大佬点拨,发现所谓的释放其实就是指SP,FP的移动。而且确实再去清零这些数据的话,不是又增加指令开销了嘛,栈空间就是方便我们使用的,也不太可能去设为非法地址或者锁啥的。这点现在终于明白了。但这里也说明了,我们用地址指针要小心,因为你不知道你访问的地址里可能装着什么惊喜,但是地址合法你访问得到,而且不会报错,这就像一把双刃剑。

 

这个是可以成功打印10的,说明使用过的栈地址是可以访问且没有做任何的清零处理之类的。

感悟:

感觉流程的挺合理的,就如果你是计算机其实你也会这么去处理。把整个stack看作一个长条,sp在顶上,然后往下存储数据,一个函数的范围,就在一个fp和一个sp之间,像一个小长方形。sp,fp你可以说是全局的,也可以聚焦到当前的函数。栈溢出的方式要么就是一个函数,整个长方形都存不下你的数据,这是很少见的。更多的是嵌套调用太深,就算每一级很小的开销,但是会逐级积累,最终溢出。再大的函数,只要你没溢出,不存在嵌套关系,一个接着一个,我们的栈也绝没问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值