【c++】——函数的堆栈调用详细过程

这篇文章,主要在汇编的角度为大家详细讲解函数的堆栈调用过程,首先我们引人一段程序,主要由此程序作为例子分析~

#include<iostream>
#include<string.h>
using namespace std;
int sum(int a, int b)
{
	int temp = 0;
	temp = a + b;
	return temp;
}
int main()
{
	int a = 10;
	int b = 20;
	int ret = sum(a, b);
	cout << "ret:" << ret << endl;
	return 0;
}

看了这段非常简单的程序,我们来思考这样两个问题。
问题一:main函数调用sum,sum执行完之后,怎么知道回到哪个函数?
问题二:sum函数执行完,回到main以后,怎么知道从哪一行指令继续运行?

为了回答以上两个问题,我们来具体分析上述程序具体的堆栈调用过程吧~

一、main函数中的前期执行过程

(1)为main函数开辟栈帧
我们都知道函数运行要在栈上开辟空间,如下图,esp和ebp分别为main函数的栈顶和栈底
,假设栈底地址为0x0018ff40
在这里插入图片描述
(2)执行语句int a = 10,int b = 20
因为函数的调用是先压栈,从右向左压参数,形参变量的内存开辟是在调用方函数开辟好的,这两条语句对应的汇编指令如下:

int a = 10 ;
mov dword ptr[edp-4],0Ah
int b =20 ;
0Ah== mov dword ptr[edp-8],14h

访问局部变量都是通过栈底指针来访问的压栈,因为栈底是高地址,往上走是低地址。形成的栈帧如下:
在这里插入图片描述
(3)执行int ret = sum(a, b); 语句

执行这段语句的时候,就要调用sum函数了,这时实参到形参传值,指令调动如下:

mov eax, dword ptr[ebp-8]先从b内存里面拿值
push eax 压栈
mov eax, dword ptr[ebp-4]先从a内存里面拿值
push eax 压栈
call  sum 函数调用指令

形成形成的栈帧如下:
在这里插入图片描述
(4)call sum指令

call sum指令的下一条指令为add esp, 8,把sum指令的下一行指令的地址 假设为0X08124458压栈.。
作用就是当sum运行完毕之后,让其知道从哪一行指令继续运行,形成形成的栈帧如下:
在这里插入图片描述

二、sum函数中的执行过程

(1)函数{之间的指令执行过程

push ebp

ebp之前一直指向main函数的栈底,进入sum函数过后,要把main函数ebp的地址入栈

mov ebp,esp

把esp赋给了ebp,让他们指向同一片区域,让ebp指向当前的栈底

 sub esp,4Ch

在给sum函数开辟栈帧空间

rep stos for

在windows vs编译器中会有如此指令将开辟的栈帧初始化为0xcccccc
在此,解释一下0xcccccc,当我们在编译器里面输入如下代码int a;cout<<a<<endl; 时,他会输入-858993460,也就是我们这里所指的0xcccccc。
形成的栈帧如下:
在这里插入图片描述

(2)sum函数内部代码执行过程

int temp = 0;---》 mov dword ptr[edp-4],0
temp = a + b;----》mov eax,dword ptr[edp+0Ch]去取b的值
             ----》add eax,dword ptr[edp+8]去取a的值并相加  a+b
			 ----> mov dword ptr[ebp-4],eax(a+b的值)放到temp中

retrun temp;//返回一个值,但是temp是出不去的,他是一个局部变量。所以:mov eax, dword ptr[ebp-4]通过寄存器带出
形成形成的栈帧如下:
在这里插入图片描述
(3)函数}之前执行的指令

mov esp,ebp

把ebp赋给了esp 回退栈帧,把sum函数的栈帧交还给系统,但是并没有对栈上的数据进行清理
对上述概念以下讲解:
我们说这样的函数是不安全的

int *func()
{
int data = 10;
return &data;
}
int main()
{
int *p = func();
cout<< *p << endl;
}
以上代码,能打印出结果,因为栈帧回退并没有对栈上的数据进行清理。但是因为函数调用完毕,栈帧回退,这个空间以及交还给系统了,以后会非法访问问题。

pop edp

edp又回到main函数的栈底

ret

ret会做两件事情:
第一件:
出栈操作,把出栈的内容放入cpu的pc寄存器(存放的是下一行要执行指令的地址)里面,cpu运行完当前指令
就会去pc寄存器看接下来要运行哪个地址所指的指令
所以ret执行完毕过后直接跳到main函数中执行下一条指令
这也就回答了问题二:sum函数执行完,回到main以后,怎么知道从哪一行指令继续运行?

第二件:esp和ebp由回到最初的位置

形成形成的栈帧如下:
在这里插入图片描述

发布了62 篇原创文章 · 获赞 7 · 访问量 2551

猜你喜欢

转载自blog.csdn.net/qq_43412060/article/details/104977921