内功修炼:栈帧的创建和销毁,助你成为编程高手

     我们都知道我们的存储区域分为栈区、堆区和静态区。

1116b20aed154737a05bc5258c1ac40b.png

      每一次函数的调用都要在栈区上分配一块空间以保存信息,这就是函数栈帧,也叫函数的调用堆栈。

      在了解栈帧的创建和销毁之前,我们还得了解一些东西:

 

   1. 寄存器:寄存器分为:eax,ebx,ecx,edx,ebp,esp

     其中ebp,esp存放的是地址,主要是用来维护栈帧的。

   2.在一些编译器中,main函数也是被其他函数调用的

 99ee684ac70c4c8e94f22743716c1c56.png

 

 

铺垫完成了,接下来进入正题:

先来一段简单的代码:

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

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;
}

我们先看看这段代码创建栈帧的整体情况是怎样的:

 

db8baa366cda44f080d2e4635ea7c187.png

 接下来我们来看看栈帧创建的具体细节

先进行以下操作

1.按下Fn+F10

 

2.右击鼠标,点击转到反汇编

2ee7b20721bc42a6ad07cac89e87931b.png

 

 

这是就来到了C语言对应的汇编代码

 

3.再次右击鼠标,点击显示符号名,将勾去掉

变成以下界面

fa727ff67ed145ad9d59b377ae7612e8.png

 

 

 

 

下面我们就看看栈帧究竟是如何创建的:

我们在前面已经说过了,main函数也是被__tmainCRTStartup调用的,所以在创建mian函数的栈帧时,栈区的情况应该如下(注意:栈帧创建都是在栈区中进行的):

ff32ab82b1c748de874a094b3076d993.png

我们看看上面反汇编语句的第1句和 第2句

1.  push   ebp  ——将ebp压入栈中(esp的指向自动改变)

ec99db80519949bea8aec3a10fa4e933.png

 

2.  mov  ebp,esp  ——将esp的地址赋给ebp

66c92d71cdd046b18b172c6f333e2fa2.png

 

 3.sub   esp,0E4h ——将esp的地址减去0E4h个字节(即十进制的228),这是在为main函数创建栈帧      开辟空间

 

 38e43659ff554eaca5ed09c5a1e6239b.png

 

 

 

 

 4.push   ebx——将ebx压入栈中

5.push    esi——将esi压入栈中

6.push    edi——将edi压入栈中

(这3个操作具体是做什么用的现在我们不用管)

57e89ec3ea82423e915bc0276fcced12.png

 

 

 

 

 7.lea   edi,[ebp+FFFFFF1Ch],此时勾上显示符号名,变为

   lea   edi,[ebp-0E4h]——lea:加载有效地址,将[ebp-0E4h]赋给edi (0E4h前面出现过)

9815687d4754435ba33b45c83c83a0d7.png

 

以下3句合着一起看,说人话就是将edi指向的地址以下39h个dword个字节的内容改为eax中的内容

8.mov      ecx,39h ——将39h(即十进制的57)赋给ecx

9.mov      eax,0CCCCCCCCh ——将0CCCCCCCCh赋给eax(不同编译器赋的值不一样)

10. ret stos    dword   ptr  es:[edi] ——一个word是两个字节,dword就是4个字节

 

57*4=228

以上相当于初始化main函数开辟的空间,这也是为什么我们有时会打印出“烫烫烫”

ef791d7060de419b8d9b80026c75228f.png

 

 

11.move   dword  ptr[ebp-8],0Ah ——将0Ah(十进制的10)放到由ebp-8开始的dword个字节中,       这是为局部变量a=10分配空间

 

aee8b550a8e046de8fe93a3e158c04a0.png

 

 

 12.move   dword  ptr[ebp-14h], 14h

 13.move   dword  ptr[ebp-20h],  0

  这两句话和上面的一样,分别是为b,c分配空间并初始化,从中我们也可以看出为它们分配的空间是不连续的。

4f4578840ea44a9181b5fdd778f75a4e.png

 

 

 14,15和16,17是两组一样的操作,其实是进行传参的操作

 

 14. mov       eax ,dword    ptr [ebp-14] ——将从地址ebp-14开始的dword个字节的内容放到eax中

 15  push       eax  ——将eax中的内容压栈

 16. mov       eax ,dword    ptr [ebp-8] ——将从地址ebp-8开始的dword个字节的内容放到eax中

 17  push       eax  ——将eax中的内容压栈

 1ebb92ad1d054afb9a3c2ee2931a868e.png

 

18.call     00C210E1 ——调用函数指令,执行该语句会将call指令下一句语句的地址(即第19句的地址)压入栈中,因为调用函数要跳转到函数内部去,当函数调用结束时还要找回来执行下一句语句

23c7869235174fadb01a5131e1692b38.png

 

 

按Fn+F11进入函数内部

 285f90c7a9dc44e7ad53494f0ede9a9e.png

 

 

 

 18.1到18.11的指令与前面main函数创建栈帧的前几个指令一样,这里是为add函数创建栈帧,然后为z分配空间

1c55aaca56ec4d229c5319c22bc51905.png

 

 

 18.12  mov     eax ,dword    ptr [ebp+8] ——将从地址ebp+8开始的dword个字节的内容放到                      eax 中,即将形参x的值放到eax中,此时eax=10

18.13   add     eax ,dword    ptr [ebp+0Ch]  ——从地址ebp+0Ch开始的dword个字节里的内容(即y          的值)与eax相加并放到eax中,此时eax=30

18.14   mov     dword    ptr [ebp-8],eax ——将eax放到从地址ebp+8开始的dword个字节中,即                为z分配的空间内,此时z=30

94039df426854a2b84f5424566797a50.png

 

18.15    mov     eax      dword    ptr [ebp-8]——将从地址ebp88开始的dword个字节的内容放到                      eax 中,即将z的值放到eax中,此时eax=30

              因为此时函数调用要结束了,且要返回z,但z在栈帧销毁后就不存在了,所以要将z的内                容放到寄存器eax中保存好

 

 

              此时add函数调用结束,开始add函数栈帧的销毁

18.16     pop     edi ——弹出一个栈顶元素放到edi中

18.17     pop     esi ——弹出一个栈顶元素放到edi中

18.18     pop     ebx ——弹出一个栈顶元素放到edi中

              18.16,18.17,18.18是一样的操作,分别将edi,esi,ebx出栈,然后分别放到edi,esi,ebx                  中,即销毁

35e0b37b239c46eda3d6eb42393152a1.png

 

 18.19   move   esp,ebp ——将ebp的值赋给esp,即继续销毁栈帧

e4f1d829fef8401eb751c53f92654893.png

 

 

18.20  pop     ebp ——弹出一个栈顶元素放到ebp中,即ebp获得ebp-main的值

ebf0fee64c6f47fc8070676628206fbe.png

 

 18.21   ret ——作用是弹出栈顶元素的地址,即call下一条指令的地址,然后跳转过去,此时又回              到以下界面,并开始执行第19句语句

              e35c52a7f59d482aadfaa4fe96bcab42.png  栈帧情况为:

 

fe395ff2a37f44e896370d1b7bcea71d.png

 

 19.将esp指向的地址加8给字节(即销毁形参x和y)

8ea01b1f980c4425aef4e24a8541e6d6.png

 

 20.mov     dword    ptr [ebp-20h],eax ——将eax的内容放到从地址ebp-20h开始的dword个字节的        中,即将eax的值放到c中,此时c=30

543d3d044b09413a90c653c8f281fb66.png

 

 后面main函数栈帧的销毁也是一样的,这里就不在过多赘述

 

  下面是前面内容的整合,从最后一张图后面往前面看

0c46bb141d6f43f5ae61127ffeb00128.png

3707cf26687544aaa3f0db495c3fd690.png 

e0129a91ea44488b8e9177419c45d1e8.png 

 

 

 

 

 

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值