函数栈帧的创建和销毁(一)

简介: 函数栈帧的创建和销毁

函数栈帧的创建和销毁


1.什么是函数栈帧?


C语言程序是以函数为基本单位的,会把一个独立的功能抽象出来,编写为单独的函数。


既然是函数,就需要考虑到如何调用?返回值又如何对待?参数如何传递?等等一系列的问题都和函数栈帧有关。


函数栈帧(stack frame)就是函数在调用过程中在程序的调用栈
(call stack)所开辟的空间,这些空间是用来存放:
 1. 函数参数和函数返回值
 2. 临时变量(保存函数的非静态的局部变量以及编译器自动生产的其他
 临时变量
 3.保存上下文信息(包括在函数调用前后需要保存不变的寄存器)


2.函数栈帧能解决什么问题?


理解函数栈帧的创建和销毁之后,以下问题就能够很好地理解


1.局部变量是如何创建的?

2.为什么局部变量不初始化内容是随机的?

3.函数调用时参数是如何传递的?传参的顺序是怎么样的?

4.函数的形参和实参分别是如何实例化(创建变量)的?

5.函数的返回值是如何返回到 main函数中的?


3.解析函数栈帧的创建和销毁


3.1栈的概念


在经典的计算机科学中,栈(stack)被定义为一种特殊的容器,用户可以将数据压入栈中(入栈, push),也可以将已经压入栈中的数据弹出(出栈, pop),但是必须遵守一条规则:先入栈的数据后出栈。


在计算机系统中,栈是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出,压栈操作使得栈增大,弹出操作使得栈减小。


在经典操作系统中,栈总是向下增长(由低地址向高地址)的。


在常见的x86或者x64环境下,栈顶由 esp寄存器进行定位。


3.2掌握相关寄存器和汇编指令


相关寄存器


eax:通用寄存器,保留临时数据,常用于返回值
ebx:通用寄存器,保留临时数
ebp:栈底寄存器
esp:栈顶寄存器
eip:指令寄存器,保存当前指令的下一条指令的地址


汇编指令


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


3.3解析函数栈帧的创建和销毁


3.3.1前言


首先需要一些预备知识才能帮助我们理解函数栈帧的创建和销毁


1.每一次的函数调用,都要为本次函数调用开辟空间,就是开辟函数栈帧的空间

2.这块空间的维护是使用了2个寄存器: esp和 ebp, ebp记录的是栈底的地址, esp记录的是栈顶的地址。


c2aaa47e9112e93edccc5dfd22db5cea_280caca3c057475d9bece713cff9c5a4.png


3.函数栈帧的创建和销毁过程,在不同的编译器上实现的方法大同小异。


3.3.2函数的调用堆栈


#include<stdio.h>
int Add(int x, int y)
{
  int z = 0;
  z = x + y;
  return z;
}
int main()
{
  int a = 3;
  int b = 2;
  int ret = 0;
  ret = Add(a, b);
  printf("%d\n", ret);
  return 0;
}


在vs2022编译器下,这段代码,调试进入Add函数后,就可以观察函数的调用堆栈,如下图


eca0748aed0a7703d1d79cb76c15521f_01dd08ac512a440a91da9c50668ff8f8.png


函数调用堆栈是反应函数调用逻辑的,可以清楚地观察到,先是调用mian函数,然后通过mian函数来调用Add函数。


接下来就从main函数的栈帧创建开始分析


3.3.3转到反汇编


调试到main函数开始执行的第一行,右击鼠标转到反汇编。


int main()
{
//函数栈帧的创建
009118B0  push        ebp  
009118B1  mov         ebp,esp  
009118B3  sub         esp,0E4h  
009118B9  push        ebx  
009118BA  push        esi  
009118BB  push        edi  
009118BC  lea         edi,[ebp-24h]  
009118BF  mov         ecx,9  
009118C4  mov         eax,0CCCCCCCCh  
009118C9  rep stos    dword ptr es:[edi]  
009118CB  mov         ecx,offset _352C9E5E_test@c (091C008h)  
009118D0  call        @__CheckForDebuggerJustMyCode@4 (091131Bh)  
//main函数中的核心代码
  int a = 3;
00EE18D5  mov         dword ptr [ebp-8],3  
  int b = 2;
00EE18DC  mov         dword ptr [ebp-14h],2  
  int ret = 0;
00EE18E3  mov         dword ptr [ebp-20h],0  
  ret = Add(a, b);
00EE18EA  mov         eax,dword ptr [ebp-14h]  
00EE18ED  push        eax  
00EE18EE  mov         ecx,dword ptr [ebp-8]  
00EE18F1  push        ecx  
00EE18F2  call        00EE13B6  
00EE18F7  add         esp,8  
00EE18FA  mov         dword ptr [ebp-20h],eax  
  printf("%d\n", ret);
009118FD  mov         eax,dword ptr [ebp-20h]  
00911900  push        eax  
00911901  push        offset string "%d\n" (0917B30h)  
00911906  call        _printf (09110D2h)  
0091190B  add         esp,8  
  return 0;
0091190E  xor         eax,eax  
}
00911910  pop         edi  
00911911  pop         esi  
00911912  pop         ebx  
00911913  add         esp,0E4h  
00911919  cmp         ebp,esp  
0091191B  call        __RTC_CheckEsp (0911244h)  
00911920  mov         esp,ebp  
00911922  pop         ebp  
00911923  ret

目录
相关文章
|
6月前
|
编译器
函数栈帧的创建和销毁
函数栈帧的创建和销毁
32 0
|
7月前
|
存储 编译器 容器
函数栈帧的创建和销毁讲解
函数栈帧的创建和销毁讲解
48 0
|
7月前
|
编译器 容器
关于函数栈帧的创建和销毁
关于函数栈帧的创建和销毁
|
存储
函数栈帧的创建和销毁(下)
函数栈帧的创建和销毁(下)
55 0
|
7月前
|
容器
函数栈帧的创建和销毁介绍
函数栈帧的创建和销毁介绍
44 0
|
7月前
|
存储 编译器
初识函数栈帧的创建与销毁(笔记)
初识函数栈帧的创建与销毁(笔记)
|
编译器 程序员 C语言
函数栈帧的创建与销毁(超详解)
函数栈帧的创建与销毁(超详解)
116 0
|
存储 缓存 编译器
函数栈帧的创建与销毁
函数栈帧的创建与销毁
45 0
|
存储 C语言 C++
你知道函数栈帧的创建和销毁吗?
你知道函数栈帧的创建和销毁吗?
78 0
|
存储 编译器 C++
深入理解内存 —— 函数栈帧的创建与销毁
深入理解内存 —— 函数栈帧的创建与销毁
133 0