文章目录
- 局部变量是怎么创建的?
- 为什么局部变量是随机值?
- 函数是怎么传参的?传参的顺序是怎么样的?
- 形参和实参是什么关系?
- 函数调用是怎么做的?
- 函数调用结束后是怎样返回的?
以上是本次要讲解的知识点,相信看完博客的你一定会有所收获。
文章内容
准备阶段:
由于各个编译器对函数的封装各有所不同,所以我们本次采用vs2013编译器进行演示和教学,此编译器对函数的封装显示的比较直观,有助于观察和学习。
寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。寄存器有很多,本次我们需要了解两个寄存器,ESP:栈指针寄存(extended stack pointer),EBP:基址指针寄存器(extended base pointer),这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。其中esp俗称栈顶指针,ebp俗称栈底指针。函数的每一次调用都需要在栈区开辟一块空间,且都是由这两个指针来维护的。
在栈区,数据是从高地址到地址值依次来存储的。
我们本次采用Add函数来演示,代码如下。
#include <stdio.h>
int Add(int x ,int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a, b);
printf("%d\n",c);
return 0;
}
按f10开始调试代码。当光标显示到如下位置是时,右击鼠标转到反汇编。
成功后如下。
之后打开监视和内存窗口。
我们可以看到第一条指令便是 push (俗称压栈:给栈顶压如一个元素),esp指向栈顶,通过esp的地址,可以看见ebp的值已经被压栈。
第二条指令是将esp的值给ebp,此时esp,ebp指向同一位置。
第三条指令是将esp减去 E4 (十六进制) 个byte。
之后的三条指令我们将 ebx esi edi (这三个东西不做过多讲解)分别压入栈顶。可以观察到的时esp的值减小12。
此时esp ebp已经开辟好了main函数所用的栈区,如下
lea(load effecitve address)为加载有效信息。这四步的意义是将从 ebp-0e4h 的位置开始向下减 39h(十六进制)个 int 类型 的双字节 全部赋值为cc
这三步的意义是将 a、b 、c 的值依次存入栈中 。
这两步是将 ebp-14 的值赋给 eax,再将eax 压栈 。ebp-14的值是什么呢?从上面可知是 20。这步操作便说明了形参是实参的一份临时拷贝。ebp-8 也是这样的。
当走到call指令时,我们注意一下call指令下一条指令add的地址,这是我们按f11进入函数,可以发现add的地址竟然被压入栈中。这是为了执行完函数回来的时候能找到下一条指令,从而继续的执行。
进入到函数后,这几步与上述的雷同,不再做过多的赘述。注意push 的ebp 时main函数的ebp 。这是为了Add函数销毁时,ebp能够直接回去。
这几步操作是将 形参的值拿过来,相加后放入 z中。并且将 z的值给到寄存器eax,即使函数销毁,eax也能将所求的和带回去。
此时依次从栈顶弹出元素,第四步将,ebp的值给到esp时,所创建Add函数的栈帧即销毁了,此时esp ebp指向同一处,下一步时将栈顶元素弹出,弹出的值便是 我们刚开始压入main函数的ebp的地址,这个ebp的地址被ebp接收,使之返回main函数的栈底。
当执行ret指令时,实际上是把我们在栈上寸的 call指令的下一条指令 弹出,从而去执行它。
这一步是将eax的值给到c。
完。