函数栈帧(函数调用执行过程剖析)

目录

函数栈帧是什么?

内存分区

寄存器

汇编指令 

 栈帧创建与销毁过程

函数执行之前的准备工作

函数执行

函数执行结束,进行函数返回

ebp回到上一个栈底

销毁形参

回到上一栈帧 

查看汇编指令

​编辑


前言

在C语言编写时,我们总会把一些功能单独写成一个函数,在主函数中调用,只需要在调用时通过函数名将实参传给形参就实现了整个函数调用过程,但实际的调用过程底层很复杂,这其中关系到函数栈帧。

函数栈帧是什么?

栈帧也叫过程活动记录,是编译器用来实现函数调用过程的一种数据结构。C语言中,每个栈帧对应着一个未运行完的函数。从逻辑上讲,栈帧就是一个函数执行的环境:函数调用框架、函数参数、函数的局部变量、函数执行完后返回到哪里等等。栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息,比如该函数的返回地址和局部变量寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)

内存分区

栈区:从高地址向低地址延伸的,主要用来存放局部变量,函数调用开辟的空间,与堆共享一段空间。

堆区:由低地址向高地址增长,动态开辟的空间就在这里(malloc,realloc,calloc,free),与栈共享一段空间。

静态区:主要存放全局变量和静态变量。 

寄存器

ebp ebp是基址指针,保存调用者函数的地址,总是指向当前栈帧栈底
esp esp是被调函数指针,总指向函数栈栈顶
esx 累加器,用来乘除法,与函数返回值(本篇主要关注第二个功能)
eax 通用寄存器,保存临时数据,常用于返回值
eip 指令寄存器,保存当前指令的下一条指令的地址

汇编指令 

mov:数据转移指令
push:数据入栈,同时esp栈顶寄存器也要发生改变
pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变
sub:减法命令
add:加法命令
call:函数调用,1. 压入返回地址 2. 转入目标函数
jump:通过修改eip,转入目标函数,进行调用
ret:恢复返回地址,压入eip,类似pop eip命令

简单来讲,esp和ebp是两个指针ebp指向当前栈帧栈底,esp指向函数栈栈顶。

函数栈帧(函数调用执行过程剖析)_第1张图片

 栈帧创建与销毁过程

#define _CRT_SECURE_NO_WARNING
#include
#include
int Add(int a, int b)
{
	int c = 0;
	c = a + b;
	return c;
}
int main()
{
	int a = 10;
	int b = 20;
	int ret = Add(a, b);
	printf("ret = %d\n", ret);
	system("pause");
	return 0;
}

 

函数执行之前的准备工作

将ADD函数需要的参数a=10和b=20入栈

 函数栈帧(函数调用执行过程剖析)_第2张图片

函数执行

保护现场(保护ebp)

由于马上要建立新的栈帧,因此对ebp和esp都得变动,为了在调用add函数后能将ebp还原到初始位置,因此需要对ebp进行保护,即将ebp的值压入栈。 

函数栈帧(函数调用执行过程剖析)_第3张图片

 创建调用函数的栈帧空间

令ebp指向当前的esp位置,并且创建一块合适大小的空间

函数栈帧(函数调用执行过程剖析)_第4张图片

 保存局部变量

将ADD函数创建的变量int c=0放入开辟的栈帧空间

 进行运算

执行c=a+b

函数栈帧(函数调用执行过程剖析)_第5张图片

函数执行结束,进行函数返回

存储返回值

达到目的ADD(a,b),现在我们希望回到main函数中继续往下执行,所以要对ADD函数桢进行销毁,但是main'函数还没有拿到ADD的返回值,此时就是前面提到的eax寄存器发挥作用,我们将返回值存储到eax寄存器中。

函数栈帧(函数调用执行过程剖析)_第6张图片

ebp回到上一个栈底

此时ebp拿到之间存储的上一栈帧栈底的值,回到相应的位置,于此同时,存储的ebp没有用了,也将被销毁。

函数栈帧(函数调用执行过程剖析)_第7张图片

销毁形参

形参进行销毁(所以,形参的改变不会影响实参,因为地址不同)

函数栈帧(函数调用执行过程剖析)_第8张图片

回到上一栈帧 

main函数拿到返回值,此时注意,这个上一栈帧代表的是什么,我们直到main其实也是一个函数,所以也有自己的栈帧,所以说这个上一栈帧就是main函数的栈帧,所以此时main函数的sum拿到eax的值,所以说,我们只有一个寄存器,因此C语言函数只能由一个返回值。

查看汇编指令

int Add(int a, int b)
{
006D1820  push        ebp      //push指令会压入ebp寄存器
006D1821  mov         ebp,esp      //move指令会把esp的值存放到ebp中,相当于产生了add函数的栈帧
006D1823  sub         esp,0C0h      //这里大可先不必多研究
006D1829  push        ebx      
006D182A  push        esi  
006D182B  push        edi  
	return a + b;
006D182C  mov         eax,dword ptr [a]  
006D182F  add         eax,dword ptr [b]  
}
int Add(int a, int b)
{
00681800  push        ebp  //push指令会压入ebp寄存器
00681801  mov         ebp,esp  //move指令会把esp的值存放到ebp中,相当于产生了add函数的栈帧
00681803  sub         esp,0CCh  //这里大可先不必多研究
00681809  push        ebx  //将寄存器ebx的值压栈
0068180A  push        esi  //将寄存器esi的值压栈
0068180B  push        edi  //将寄存器esi的值压栈
	int t = 0;
0068180C  mov         dword ptr [t],0  
	t = a + b;
00681813  mov         eax,dword ptr [a]  //exa通用寄存器,保留临时数据a
00681816  add         eax,dword ptr [b]  //exa通用寄存器,保留临时数据a
00681819  mov         dword ptr [t],eax  //把exa寄存器的值交给t
	return t;
0068181C  mov         eax,dword ptr [t]  //将t的值交给eax寄存器
}

函数栈帧(函数调用执行过程剖析)_第9张图片

 创建add函数栈帧,创建临时变量,计算后将结果存在eax,由eax返回,销毁add栈帧。

你可能感兴趣的:(C++学习,C++,C,开发语言)