【C语言】可能是csdn最易懂的--函数栈帧(图解版)

函数栈帧

  • 前言
  • 一、本文章的学习目标
  • 二、实验准备
    • 1.一个简单的代码- -实现两个数相加
    • 2.一点要用到的小知识
  • 2.解析代码
    • 1.函数调用前的准备工作
    • 2.调用Add函数前的栈帧
    • 3.Add函数的堆栈
    • 4.返回时的堆栈
  • 整体的图
  • 总结


前言

文章篇幅较长,如有需要请先收藏❤❤❤
在学习c语言的过程中对于一些函数的执行很多时候我们都是不清楚,不了解的,这其实对于学习是不好的,本篇就大致的叙述函数栈帧的创建和销毁。


一、本文章的学习目标

了解局部变量的是如何创建
为什么局部变量的值是随机值–程序为何经常出错显示的是烫烫烫
函数是怎么传参的,传参的顺序是怎样的?
形参和实参是什么关系?
函数调用是怎样做到的?
函数调用是结束后怎么返回的?
仔细观察这篇文章,然后尝试解答以上的问题

二、实验准备

1.一个简单的代码- -实现两个数相加

代码如下

#include
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);
	return 0;
}

2.一点要用到的小知识

我所用的是vs2013环境下得到的结果
这段代码应该是人人都能读得懂的,我们先按住f11,右击鼠标到反汇编【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第1张图片
在这里要给大家介绍一点基本知识:寄存器
我们常见的寄存器有:eax,ebx,ecx,edx,ebp,esp,其中的ebp,esp这两个寄存器存放的是地址,用来维护函数栈帧的!!

知道了这些,我们就可以进行下一步了,让我们一起读读汇编代码

2.解析代码

首先呢,main函数又是谁调用的呢?我们按住f10,就可以看到
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第2张图片
我们向上查找就能看到是由__tmainCRTStartup调用我们的main函数的【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第3张图片
而__tmainCRTStartup又是mainCRTStartup函数调用的【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第4张图片

所以我们打开调用堆栈时,可以看到调用的顺序【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第5张图片
从这里接下来我们开始讲解:

1.函数调用前的准备工作

【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第6张图片
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第7张图片
第一步的push操作会让esp往上走,同时我们也可以从从下图看出esp指向的也确实是ebp的值【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第8张图片
第二步的mov其实也就可以理解成赋值,将esp的值给到ebp【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第9张图片
第三步就是sub就是将 esp-0E4h个字节
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第10张图片
第三步将这三个数据入栈
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第11张图片
第四步:lea操作就是加载地址详细看下图【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第12张图片
第五步:将39h给到我们的ecx,16进制数CCCCCCCC给到eax,我们所进行的其实就是从edi这个位置开始,往下16进制的39个dword(一个dword4字节)置成eax,此时也就是从之前的ebp-oE4h的位置往下放入值【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第13张图片在这里插入图片描述
即:
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第14张图片
相信看到这里,你就能够明白main函数当中我们走到这里,连代码的第一行都还没走到,但看到这里,往后看你就能够明白为什么局部变量不初始化的话是随机值了


2.调用Add函数前的栈帧

接下来我们进行讲解在Add函数调用之前编译器还帮我们干了什么
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第15张图片
**第一步:**这里的mov也就是将 0Ah–十进制的10,给到我们的ebp-8
这个地址,画图表示为,其实到了这里,也就能解释了,局部变量不初始化,默认放着就是我们的0xCCCCCCCC也就是烫烫烫【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第16张图片
第二步:将数据mov到相应位置,逻辑相同,我们这边就一次弄好【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第17张图片
在进入call命令前的几个步骤就是压栈【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第18张图片

我们这里将call命令前的四个命令的图在画一画:其中的esp在每次push命令都会指向新压入的数据,这里1,2和3,4的逻辑类似,都是先把值放在eax这个寄存器当中,在压栈,图为下面【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第19张图片
接下来进入Add函数内部,我们应该先点击f11进入函数内部
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第20张图片
进入Add函数内部,这时会发现一开始的指令差不多,也是先push,mov,sub,push这三个数据之后,再对Add的空间进行类似初始化的操作,然后才到我们所写的代码指令,因为这里的逻辑和上面相差不大,我们就直接给出图大家可以对一对,重点讲解返回时的逻辑


3.Add函数的堆栈

**第一步:**由于图画的太大了,所以我们这里展示Add栈帧中的图画,当执行到mov之前都和前面的逻辑类似,就一起花了
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第21张图片
然后再将esp指向栈顶

第二步:仔细看这里mov是将ebp+8的值(b的拷贝)放到eax,然后add中先让ebp+0Ch的值与eax相加放到eax当中,最后mov将eax放入ebp-8,即实现了相加
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第22张图片

【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第23张图片
接下来进行Add函数的返回操作

4.返回时的堆栈

【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第24张图片
接下来mov操作:
第一步
将eax保存 ebp-8这个位置的值,这里面储存的就是我们的值30,出了作用域之后变量z的空间还给内存,但是我们的寄存器eax保存了结果!!
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第25张图片
第二步
mov操作
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第26张图片
第三步:在进行pop ebp操作的时候,将当前数据pop出来并且将值给到ebp当中,所以ebp又能够重新找回main函数的栈底,这个过程非常重要!!
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第27张图片
最后一步:结果返回!!恭喜你,学到这里也就初步了解了函数栈帧大概什么样的,接下来可以尝试回答一下文章开头的问题
【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第28张图片


最后附上栈帧图

整体的图

【C语言】可能是csdn最易懂的--函数栈帧(图解版)_第29张图片


总结

函数栈帧其实是一个非常重要的知识,对于上面讲述有错误的同学可以私信我,喜欢的话可以给个一键三连哟!!

你可能感兴趣的:(C语言,数据结构)