西安高端网站制作公司哪家好如何做百度竞价推广
【手把手带你搞定】函数栈帧的创建和销毁
- 栈帧
- 寄存器
- 一些调试准备
- 调试过程
相信在前期C语言的学习的时候,大家一定都有下面这些困惑。
那么今天我们用这篇文章一次性解决以下所有问题:
- 局部变量是怎么创建的?
- 为什么局部变量的值是随机值?
- 函数是怎么传参的?
- 传参的顺序是怎样的?
- 形参和实参是什么关系?
- 函数调用是怎么做的?
- 函数调用结束后怎么返回的?
其实这些问题都离不开一个关键:
函数栈帧的创建和销毁,因为局部变量和函数的调用都是在栈区上创建的。
所以,
== 弄明白函数栈帧的创建和销毁 = 修炼了自己的内功 ==
不仅一次性解决上面的所有问题,而且后期学习更多知识也能更快更好吸收。
注意:在不同的编译器下,函数调用的过程中栈帧的创建是略有差异的,但是大体的逻辑是一样的。
今天我们还是在VS2019的环境下进行学习和观察。
当然,这里推荐使用VS2013的版本进行观察,因为越高级的版本,编译器内部的优化越多,则后台的操作就会越复杂,越不利于我们把函数栈帧的创建与销毁这一过程抽离出来。(后面你就知道了……)
所以,以下如果有一些地方是在VS2019中看不到的,我会把在VS2013中的结果告诉大家作补充;或者有一些地方VS2019比VS2013复杂的地方,我就简单带过啦~
栈帧
什么是栈帧?
寄存器
什么是寄存器?
在编译器中,我们经常可以见到下面这些寄存器:
eax、ebx、ecx、edx、ebp、esp
下面,我们要重点关注的是ebp、esp这2个寄存器。
在函数栈帧中,ebp、esp这2个寄存器中存放的是地址,而这两个地址就是用来维护函数栈帧的。
首先,我们知道,每一次函数调用时,程序都要在栈区中为这个函数开辟一块空间。而ebp和bsp就是用来维护这块空间的。
我们用一段代码来举例:
那么在栈区中,程序就为main函数开辟了一块空间,这块空间就是main函数的函数栈帧。
而寄存器ebp中的地址始终指向当前正在调用的函数的栈帧的底部。
寄存器esp则始终指向函数栈帧的顶部。
从以上,我们大概可以清楚esp和ebp是其实就是用来划定当前程序调用的函数的栈帧范围的。
当前程序在调用哪个函数,esp和ebp就分别指向这个函数栈帧的顶部和底部。
所以他们又分别被称为栈顶指针和栈底指针。
当程序调用新的函数,需要开辟新的空间时,就会通过esp和ebp的向上移动来使用新的空间。
一些调试准备
下面我们通过上面这段程序的代码来进行了解。
首先我们进入调试,打开调试窗口中的调用堆栈。
其实,在C语言中,main函数是程序执行的入口,所以,在实行我们的代码之前,程序首先要调用main函数。
那么main函数是被谁调用的呢?
因为在VS2019的环境中,main函数的调用比较复杂,所以我们在直接调用堆栈中看不到是谁调用的。
但是在VS2013的环境下,如果我们再按下F10,就能看到其实main函数是被一个叫__tmainCRTStartup的函数调用的,而这个函数又是被mainCRTStartup函数调用的。
所以,在调用main函数之前,栈区中应该已经为这两个函数分别开辟了空间。
所以,在调用main函数之前,栈区中应该是这样的: