函数栈帧的创建和销毁

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

前言

对于编程这件事来说,函数的调用在代码中是必不可少的,那函数在内存中到底是如何创建和销毁的,下面我将写一个简单的函数为大家演示。

为了更加清楚的理解函数栈帧的创建和销毁,将使用VS2013为大家演示(不同版本的vs演示的效果可能不同),

一 认识寄存器

根据百度百科介绍,寄存器是中央处理器内的组成部分。 寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。 简单来说, 寄存器就是存放东西的 。 从名字来看,跟火车站寄存行李的地方好像是有相似。

在本次函数栈帧的创建和销毁要到寄存器有:

eax

ebx

ecx

edx

esp:栈顶指针

ebp:栈底指针

对于这四个寄存器知道有这些寄存器即可,对于esp和ebp是用来维护函数的。

二 Add函数栈帧的创建和销毁

#define  _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
 
int Add(int x, int y)
{
  int c = 0;
  c = x + y;
  return c;
}
int main()
{
  int a = 10;
  int b = 20;
  int ret = Add(a, b);
  printf("ret = %d\n", ret);
  return 0;
}

在调试之前,我们要想一个问题,main函数是程序的入口,但mian函数也是一个函数,那么它又是被什么函数调用的呢?

我们可以调用堆栈进行观察:

这时我们发现main函数是被__tmainCRTStartup函数调用,而__tmainCRTStartup函数又是被mainCRTStartup函数调用。

main函数的调用

int main()
{
00881410  push        ebp  
00881411  mov         ebp,esp  
00881413  sub         esp,0E4h  
00881419  push        ebx  
0088141A  push        esi  
0088141B  push        edi  
0088141C  lea         edi,[ebp+FFFFFF1Ch]  
00881422  mov         ecx,39h  
00881427  mov         eax,0CCCCCCCCh  
0088142C  rep stos    dword ptr es:[edi]  
  int a = 10;
0088142E  mov         dword ptr [ebp-8],0Ah  
  int b = 20;
00881435  mov         dword ptr [ebp-14h],14h  
  int ret = Add(a, b);
0088143C  mov         eax,dword ptr [ebp-14h]  
0088143F  push        eax  
00881440  mov         ecx,dword ptr [ebp-8]  
00881443  push        ecx  
00881444  call        008810E1  
00881449  add         esp,8  
0088144C  mov         dword ptr [ebp-20h],eax  
  printf("ret = %d\n", ret);
0088144F  mov         esi,esp  
00881451  mov         eax,dword ptr [ebp-20h]  
00881454  push        eax  
  printf("ret = %d\n", ret);
00881455  push        885858h  
0088145A  call        dword ptr ds:[00889114h]  
00881460  add         esp,8  
00881463  cmp         esi,esp  
00881465  call        0088113B  
  return 0;
0088146A  xor         eax,eax  
}
0088146C  pop         edi  
0088146D  pop         esi  
0088146E  pop         ebx  
0088146F  add         esp,0E4h  
00881475  cmp         ebp,esp  
00881477  call        0088113B  
0088147C  mov         esp,ebp  
0088147E  pop         ebp  
0088147F  ret  

这些都是调用main函数的准备工作

其中的push ebp指的是压栈,就是把ebp中的指向的值给ebp。

mov ebp,esp.这里是指把esp中的值给ebp

sub         esp,0E4h 是指把esp中的值减去0E4h。

push        ebx  

push        esi  

push        edi

压栈三个值

 lea         edi,[ebp-0E4h]  

mov         ecx,39h  

mov         eax,0CCCCCCCCh  

rep stos    dword ptr es:[edi]  

这些反汇编就是将mian函数的值,从edi开始下面初始化39次,都初始化为CCCCCCCC

所以,为什么有时候会打印出 烫烫烫烫烫烫,因为变量在初始化时内存里面放的CCCCCC的值。

   int a = 10;

mov         dword ptr [ebp-8],0Ah  

这里代码的意思是将a的值放到ptr地址处

mov         dword ptr [ebp-8],0Ah  

   int b = 20;

同理是将b的值放到[ebp-8]的地址处

下面就进行函数的传参,那函数传参又是在内存中进行的呢?

mov         eax,dword ptr [ebp-14h]  

这里就是将[ebp-14h](b的值)中的值放到eax中

push        eax

将eax进行压栈

mov         ecx,dword ptr [ebp-8]  

这里就是将[ebp-14h](a的值)中的值放到ecx中

push        ecx

将ecx进行压栈

下面就进行函数调用

00881444  call        008810E1  

为了调用的函数参数能够返回,call指令下一条指令的地址。

下面进入到Add函数中的反汇编。

下面这些又是在干什么呢?其实是在维护Add函数的栈帧。

同main函数的维护一样,都是要函数栈帧的准备工作,分配内存,初始化内存。CCCCCCCCCCCCC

int Add(int x, int y)
{
008813C0  push        ebp  
008813C1  mov         ebp,esp  
008813C3  sub         esp,0CCh  
008813C9  push        ebx  
008813CA  push        esi  
008813CB  push        edi  
008813CC  lea         edi,[ebp+FFFFFF34h]  
008813D2  mov         ecx,33h  
008813D7  mov         eax,0CCCCCCCCh  
008813DC  rep stos    dword ptr es:[edi]  
  int c = 0;
008813DE  mov         dword ptr [ebp-8],0  
  c = x + y;
008813E5  mov         eax,dword ptr [ebp+8]  
008813E8  add         eax,dword ptr [ebp+0Ch]  
008813EB  mov         dword ptr [ebp-8],eax  
  return c;
008813EE  mov         eax,dword ptr [ebp-8]  

那函数的值又是怎么返回的呢?形参不是会销毁吗?

mov         eax,dword ptr [ebp-8]  

这里是将[ebp-8](30)的值保持在eax中。

008813F1  pop         edi  

008813F2  pop         esi  

008813F3  pop         ebx  

008813F4  mov         esp,ebp  

008813F6  pop         ebp  

008813F7  ret  

那这些又是指的是什么?

pop指的是出栈的意思,使得esp不断加加。

其中的mov esp,ebp是将ebp的值给esp,在 pop         ebp  这样esp和ebp维护的空间重新指向main函数。ret是回到函数调用,call指令的下一个地址。

这样我们就是完成了main函数的创建和Add函数创建和销毁。而main函数的销毁和Add函数的销毁是类似的。

下面我们来回答几个问题,来运用我们今天学习的。

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

首先为函数分配好空间,在为函数初始化,最后在为局部变量在分配好的空间中,分配好地址。

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

因为我们在为函数初始化的时,就是初始化的随机值。

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

将参数存入eax寄存器中,传参的顺序是从右到左。

4函数调用的结果是如何返回的?

通过call指令记住下一条指令的地址,从而找到main函数,在通过寄存器将返回值带到,main函数中。

总结

通过对Add函数创建和销毁的理解,我们相信大家对于函数在内存中的分配会有更加深刻的理解。在下面的博客中,博主会给大家带来更多知识,希望大家不要错过噢!


相关文章
|
5月前
|
编译器
函数栈帧的创建和销毁
函数栈帧的创建和销毁
28 0
|
6月前
|
存储 编译器 容器
函数栈帧的创建和销毁讲解
函数栈帧的创建和销毁讲解
40 0
|
6月前
|
编译器 容器
关于函数栈帧的创建和销毁
关于函数栈帧的创建和销毁
|
6月前
|
容器
函数栈帧的创建和销毁介绍
函数栈帧的创建和销毁介绍
38 0
|
存储
函数栈帧的创建和销毁(下)
函数栈帧的创建和销毁(下)
52 0
|
11月前
|
编译器 程序员 C语言
函数栈帧的创建与销毁(超详解)
函数栈帧的创建与销毁(超详解)
101 0
|
12月前
|
存储 缓存 编译器
函数栈帧的创建与销毁
函数栈帧的创建与销毁
37 0
|
存储 C语言 C++
你知道函数栈帧的创建和销毁吗?
你知道函数栈帧的创建和销毁吗?
75 0
|
存储 编译器 C++
深入理解内存 —— 函数栈帧的创建与销毁
深入理解内存 —— 函数栈帧的创建与销毁
120 0
|
编译器 C语言 容器
函数栈帧的创建和销毁(一)
函数栈帧的创建和销毁
115 1