C语言内功修炼【函数栈帧的创建和销毁】

目录

 ⌚一、为main()函数开辟栈帧

1.main()函数被调用

2.寄存器和栈区

3.main()函数栈帧的开辟

4.动态演示图 

⏰ 二、在main()函数中创建变量

1.变量的创建

2.动态演示图

⚾️三、调用Add()函数前的准备

         ⛳1.调用Add()函数

2.动态演示图

⚽ 四、为Add()函数开辟栈帧

1.Add函数栈帧的开辟

 2.动态演示图

 ✏️ 五、在Add()函数中创建变量并运算

1.变量的创建并运算

2.总结

✒️ 六、Add()栈帧的销毁

1.销毁

2.动态演示图


⌚ 一、为main()函数开辟栈帧

1.main()函数被调用

       main()函数,也就是我们所说的主函数,mian()函数也是一个函数,它的使用也是需要调用的,那么main()函数是被谁调用的呢?

按F10进行调试,直到主函数return 0被返回,即可出现以下界面

 

        可以看到main() 函数是被 __tmainCRTStartup() 函数调用的;__tmainCRTStartup() 函数是被 mainCRTStartup() 函数调用的。 

2.寄存器和栈区

⚠️

注意:espebp这两个寄存器存放的是地址,是用来维护函数栈帧的。

正在调用哪个函数,espebp维护的就是哪个函数的函数栈帧

⚠️

注意:栈区的使用习惯是先消耗高地址再消耗低地址

 在栈区放入一个元素叫压栈,删除栈区的元素叫出栈。

 ⚠️

注意:只能从栈顶进行压栈或出栈操作,所以就造成了先进的后出后进的先出(进指的是压栈,出指的是出栈)

3.main()函数栈帧的开辟

push ebp


将 ebp 的值放到 __tmainCRTStartup() 函数的函数栈帧的上面 (ebp 的值为该函数栈帧的栈底指针

执行 push 操作后栈顶指针 esp 就会往上移。因为上面是低地址,esp往上移动后值会减小

 ⚠️

注意:每一次执行push操作之后,esp都会向上移动(低地址)

 

move     ebp , esp

将 esp 的值赋给 ebp,ebp 就会向上移动指向 esp 所指的位置

 

 sub      esp , 0E4h

 esp - 0E4h (八进制数,转换为十进制值为:228),将esp减去一个228

 

push ebx

push esi

push edi

⚠️

注意:

这三个push压栈压了三个值,具体是什么不需要知道,我们只需要知道每一次压栈esp(栈顶指针)都会向上移动一次,压栈三次也就移动了三次。

 

 根据图可以看出来,三次push之后确实是把ebx、esi、edi的值压进去了。

当执行完push edi之后,esp就会指向edi的值。

lea     edi , [ ebp-0E4h ]

将 [ ebp-0E4h ] 加载到 edi 中(将 [ ebp-0E4h ]这个值放到 edi 里面)。

 

       ebp-0E4h的值为三次push前esp指向的值,三次push前esp指向的是main()函数的函数栈帧的栈顶;所以ebp-0E4h的值为main()函数的函数栈帧的栈顶的值,即edi中存放的的值为main()函数的函数栈帧的栈顶的值

mov   ecx,39h:  将 39h 放到 ecx 中。

mov   eax,0CCCCCCCCh:将 0CCCCCCCCh 放到 eax 中

rep stos     dword ptr es:[edi]:从edi ( 此时 edi 中存放的是main函数栈帧栈顶处的值 ) 开始向下将39h这么大的空间中的值全部初始化为0CCCCCCCCh(eax)。

⚠️

注意:dword:double word,word表示两个字节,double word就表示4个字节。


到这里main()函数的函数栈帧已经开辟完毕了。

4.动态演示图 

 

⏰ 二、在main()函数中创建变量

1.变量的创建

mov    dword ptr [ebp-8],0Ah

将 0Ah(0Ah 转化为十进制就是 10)放到 ebp - 8 这个位置。

因为在定义变量a的同时赋初值为10,所以执行 int a = 10; 会将10放到变量 a 所在的内存中。

⚠️注意:

如果在定义变量的时候没有赋初值,那么变量 a 所在的内存中存放的就是cc cc cc cc所以在定义变量的时候不赋初值打印出来的就是随机值cc cc cc cc这个值是由编译器存放的,不同编译器存放的值不同)
 

 mov     dword ptr [ebp-14h],14h

将 14h(14h 转化为十进制就是 20)放到 ebp - 14h 这个位置。

此时ebp 的值为 0x012FFAA0 所以 ebp - 14h 的值为 0x012FFA8C,可以发现编译器为变量 b 开辟的空间距离变量 a 是跳过8个字节。

⚠️

注意:具体是跳过几个字节主要取决于编译器,有些编译器是紧挨着开辟空间的。

mov     dword ptr [ebp-20h],0

将 0 放到 ebp - 20h 这个位置。 

 此时ebp 的值为 0x00AFF9D8  所以 ebp - 20h 的值为 ,可以发现编译器为变量 c 开辟的空间距离变量 b 是跳过8个字节。

a,b,c三个变量创建完毕,开始调用Add()函数

2.动态演示图

⚾️三、调用Add()函数前的准备

1.调用Add()函数

mov     eax,dword ptr [ebp-14h]

将 ebp-14h 中存放的值放到 eax 中。ebp-14h 是变量 b 所在空间的地址,也就是将 20 放到 eax 中。

push     eax

将 eax 放到 esp 指向的地址的上面,esp ( 栈顶指针) 向上移动指向 eax 。


mov     ecx,dword ptr [ebp-8]

将 ebp-8 中存放的值放到 ecx 中。ebp-8 是变量 a 所在空间的地址,也就是将 10 放到 ecx 中。

push ecx

将 ecx 放到 esp 指向的地址的上面,esp ( 栈顶指针) 向上移动指向 ecx 。


⚠️注意:

        执行 call 指令以后会将 call 指令的下一条指令的地址压栈。这里将地址压栈是为了记住 call 指令的下一条指令的地址,记住这个地址是为了调用完 Add函数后返回到这个地址,再从这个地址继续往下执行。

2.动态演示图

 


⚽ 四、为Add()函数开辟栈帧

1.Add函数栈帧的开辟

与 main 函数栈帧的创建相同,先为 Add 函数栈帧做准备工作,再执行C语言代码。

push     ebp 

将 ebp 的值放到 esp 指向的地址的上面,esp ( 栈顶指针) 向上移动指向 ebp(此时ebp 中存放的是 main 函数栈帧栈底的地址)。

mov     ebp , esp

将 esp 的值赋给 ebp,ebp 就会向上移动指向 esp 所指的位置。

 

sub     esp,0CCh

esp - 0CCh(八进制数,转换为十进制值为:204)。

push ebx

push esi

push edi

 

 lea     edi,[ ebp-0CCh ]

将 [ ebp-0CCh ] 加载到 edi 中(将 [ ebp-0CCh ]这个值放到 edi 里面)。

  

mov     ecx,33h:将 33h 放到 ecx 中

mov     eax,0CCCCCCCCh:将 0CCCCCCCCh 放到 eax 中

rep stos     dword ptr es:[edi]:从edi ( 此时 edi 中存放的是Add函数栈帧栈顶处的值 ) 开始向下将空间中的值初始化为0CCCCCCCCh(eax)(每次操作4个字节),总共初始化 33h(ecx) 次。


mov     dword ptr [ebp-8],0

将 0 放到 ebp - 8 这个位置。

 2.动态演示图


 ✏️ 五、在Add()函数中创建变量并运算

1.变量的创建并运算

mov     eax,dword ptr [ebp+8]

将 ebp+8 里面的值放到 eax 中,ebp+8存放的是变量 a 传参(形参 x)的值,也就是将 10 放到 eax(寄存器)中。

add     eax,dword ptr [ebp+0Ch]

将 ebp+0Ch 里面的值与 eax 中的值相加然后存放到eax中。

⚠️注意:

ebp+0Ch 存放的是变量b传参(形参 y)的值,eax 中的值是10,就是将 20 + 10 的结果存放到 eax 中。

mov     dword ptr [ebp-8],eax

将 eax 中的值放到 ebp-8 的位置上。

2.总结

函数在调用计算的时候并没有创建形参,是main函数在调用Add函数之前将参数传过去,在传参的过程中将形参 a 与形参 b 进行压栈。压栈是先 push b 再 push a, 所以传参是先传 b 再传 a,所以参数是从右向左传的。

在函数内部进行计算的时候,形参并没有在 Add 函数中被创建,而是通过寻找传参传过去的空间(压栈压进去的空间),再对里面的值进行计算。计算后的结果放变量 z 中。

形参是实参的一份临时拷贝。通过这个按例可以很清楚的知道改变形参并不会影响实参。

 

3.动态演示图


✒️ 六、Add()栈帧的销毁

1.销毁

mov     eax,dword ptr [ebp-8]

将 ebp-8 里面的值放到 eax 中。

 ⚠️注意:

 执行 return z; 以后,变量 z 的空间就会被销毁,在销毁之前将里面的值放到 eax(寄存器)中,这样 Add 函数计算后值就可以返回到 main 函数中。

pop edi

pop esi

pop ebx

⚠️注意:每执行一次 pop 指令就会弹出一个值,esp 的值就会向下移动一次,执行完三次 pop 指令后,esp 就会向下移动三次。

mov     esp,ebp

将 ebp 的值赋给 esp,esp 就会向下移动指向 ebp 所指的位置

pop     ebp

将栈顶 ebp 的值弹出。当前 ebp 存放的是 main 函数栈帧栈底的地址。执行 pop     ebp 指令后,ebp 就会指向 main 函数栈帧的栈底。

 ⚠️注意:

将main函数栈底的值提前保存是为了Add 函数调用结束后,随着Add栈帧的销毁,main 函数的栈顶是很容易找到的,但是main 函数的栈底不容易找到,所以将main函数的栈底地址提前保存。


 ret

返回到 main 函数。执行 ret 指令后会从栈顶弹出 call 指令的下一条指令的地址,回到 main 函数的函数栈帧中,继续从 call 指令的下一条指令开始执行。

add     esp,8

esp+8,esp 向下移动,当esp向下移动指向 esp+8 的地址时,形参 x 、y 的空间被释放。

mov     dword ptr [ebp-20h],eax

将 eax 中存放的值放到 ebp-20h (转换成十进制:32) 中。 

 

2.动态演示图

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与大师约会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值