函数栈帧的创建和销毁

基础知识介绍和引入
//不同的编译器下,函数调用过程中栈帧的创建是略有差异的
栈区下面是高地址,上面是低地址;
在调用时,栈区是从高地址向低地址使用的
//
寄存器:集成到CPU上
ebp、esp这两个寄存器中存放的是地址
这两个地址是用来维护函数栈帧的
调用哪个函数就维护哪个栈帧
每一个函数调用,都要在栈区创建一个空间,称这块空间为函数的函数栈帧
ebp存了这块空间的高地址,称ebp为栈低指针
esp存了这块空间的低地址,称esp为栈顶地址
在VS2013中,main函数也是被其他函数调用的
函数栈帧的创建和销毁_第1张图片

//push:压栈:在栈顶放一个元素
//pop:出栈:从栈顶删除一个元素
引入:
函数栈帧的创建和销毁_第2张图片

代码
函数栈帧的创建和销毁_第3张图片

1.
为main函数创建空间
push         ebp:压栈
在栈顶部放一个值ebp
然后esp向上移动一位
函数栈帧的创建和销毁_第4张图片

mov         ebp,esp
mov         a,b:
把后面b的值赋给前面的a
sub         esp,0E4h
把esp减去十六进制的0E4h
esp地址变小,指向上面
函数栈帧的创建和销毁_第5张图片

紫色部分就是为main函数预开辟的空间
lea         edi ,[ebp-0E4h]
load effective address:
加载有效地址
[ebp-0E4h]这个地址放入edi中
lea         edi ,[ebp-0E4h]
mov       ecx,39h
mov       eax,0CCCCCCCCh
rep         stos dword ptr es:[edi]
word占两个字节,dword:double word占四个字节
rep         stos dword ptr es:[edi]
意思是:把从edi 向下到 39h之前的所有地址的编号改为0CCCCCCCCh
2.
创建局部变量
函数栈帧的创建和销毁_第6张图片

int a = 10;
mov         dword ptr [ebp-8],0Ah
0Ah赋给[ebp-8]
//此处的8单位是字节
已知:一个地址占四个字节
[ebp-8]就是ebp向上两个地址的位置
//
如果不初始化a,那么在[ebp-8]这块空间存储的就是0CCCCCCCCh
这也就是为什么有时候在打印时会打印出随机值或者烫
int b = 20;
mov         dword ptr [ebp-14h],14h
int c = 0;
mov         dword ptr [ebp-20h],0
函数栈帧的创建和销毁_第7张图片

3.1
传参
函数栈帧的创建和销毁_第8张图片

[ebp-14h]这个地址的值赋给eax
把eax放到栈顶
[ebp-8]这个空间的值赋给ecx
把ecx放到栈顶
3.2
调用add函数:
call         00C210E1
按F11,在进入函数的同时
在栈顶存储00C21450
函数栈帧的创建和销毁_第9张图片

call指令会在存储call指令下一条指令的地址
目的是在执行完call指令后能返回继续执行下一条指令
4.
进入add函数
函数栈帧的创建和销毁_第10张图片

a.
为add函数申请内存空间
把main函数的ebp放在栈顶
把esp的值赋给ebp
把esp减去0CCh,使得esp地址向上移
b.
与2.步骤相同
初始化成CCCCCCCCh
c.
函数栈帧的创建和销毁_第11张图片

此处的[ebp+8]就是ecx,即3.1传参传进来的a,值为10
[ebp+0Ch]就是[ebp+12]就是b
line 4:
把ecx的值赋给eax
line 5:
把eax和[ebp+0Ch]的值相加
line 6:
把eax的值赋给[ebp-8],即z
由此可知,3.1这一步在调用之前就进行传参,将形参b、a通过压栈传到栈顶的目的了
即:
形参不是在add函数内部创建的(并没有另外在add函数内部存储参数,而是直接利用了之前传入的参数)
这很好地解释了:
形参是实参的一份临时拷贝
以及
在传值调用时,改变形参不会影响实参
5.
返回z
line 8:
把[ebp-8]的值赋给eax
注意:eax为寄存器,不会随着函数的结束而销毁
6.1
初步清理:
函数栈帧的创建和销毁_第12张图片

pop         edi
pop:弹出
意思是:把栈顶的元素弹出,然后存入edi这个寄存器中
这样栈中的元素就少了一个,esp的位置也向下移动一位
pop         esi
pop         ebx
同样是弹出栈顶的元素,分别存入esi、edi这两个寄存器中
函数栈帧的创建和销毁_第13张图片

6.2回收add函数的内存空间
a.
mov         esp,ebp
把ebp赋给esp
b.
pop         ebp
此时栈顶是main 函数的ebp
这一步是为了找到main函数的栈低
esp同时向下移动一位
这样就回到了原来的main函数
c.
此时的栈顶是call指令下一条指令的地址
ret 
ret 指令执行后就回到了call指令的下一条指令处
7.
函数栈帧的创建和销毁_第14张图片

line 7:
esp+8
目的是将esp向下移动两位
将形参所占空间释放掉
line 8:
把寄存器eax的值(此时eax中寄存着add函数的返回值)赋给[ebp-20h],即c
line 9:
打印c
8.
回收main函数的内存空间
函数栈帧的创建和销毁_第15张图片

与6.2相同
不再进行解释
结语:
不由得同时感叹编程的复杂和有趣。

你可能感兴趣的:(C语言知识点,c语言)