C语言的函数栈帧

⭐️前面的话⭐️

📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创,CSDN首发!
✉️坚持和努力一定能换来诗与远方!
💬参考在线编程网站:🌐牛客网🌐力扣
博主的码云gitee,平常博主写的程序代码都在里面。
博主的github,平常博主写的程序代码都在里面。
🙏作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!


1.寄存器

寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC)。在中央处理器的算术及逻辑部件中,寄存器有累加器(ACC)。

本文不过多深入了解寄存器,只要知道寄存器集成在CPU之中和以下几个寄存器就可以了。
在这里插入图片描述

2.函数栈帧

2.1函数栈帧的概述

C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构

函数栈帧的创建和销毁是基于栈所实现的。
所谓栈,是一种数据结构,具有先进后出的特点。在函数栈帧创建过程中,内存从高地址开始使用,越后面创建的函数栈帧或压栈数据,所存储的空间地址越低。
在这里插入图片描述
想要更深入了解这一数据结构,欢迎访问博主另一篇文章:栈和队列介绍和基本功能从理论到实践

2.2函数栈帧创建过程

2.2.1被调用的main函数

main函数是会被其他函数调用的,在不同编译器中调用main的函数也不同。
在VS2019中,main函数会被下面几个编译器内置的函数链式访问。
在这里插入图片描述
首先,这个invoke_main函数会返回main函数的返回值。

    static int __cdecl invoke_main()
    {
        return main(__argc, __argv, _get_initial_narrow_environment());
    }

然后会有一个名叫main_result的int const类型变量接收,invoke_main函数的返回值,也就是main函数的返回值,最后这个main_result会被编译器其他函数所使用。

	int const main_result = invoke_main();

在这里插入图片描述
在这里插入图片描述
函数栈帧的结构如下
esp为栈顶指针
ebp为栈底指针
它们共同维护函数栈帧
在这里插入图片描述

2.2.2函数栈帧创建与销毁的过程

对于函数栈帧的创建与销毁,我们以一个简单的程序为例。

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

int add(int a, int b)
{
	int d = a + a;
	return d;
}

int main()
{
	int a = 2;
	int b = 6;
	int c = 0;

	c = add(a, b);

	printf("%d\n",c);
	return 0;
}

由于编译器中有其他函数调用main,所以在main函数栈帧创建前,编译器中调用main的函数栈帧就已经创建了,esp,ebp会在如图位置
在这里插入图片描述

00892580  push        ebp  //ebp压栈
00892581  mov         ebp,esp  //将esp的值赋给ebp
00892583  sub         esp,0E4h  //将esp的值减0E4h,也就是为main函数栈帧分配空间

在这里插入图片描述

00892589  push        ebx  //ebx压栈
0089258A  push        esi  //esi压栈
0089258B  push        edi  //edi压栈
0089258C  lea         edi,[ebp-24h]  
0089258F  mov         ecx,9  
00892594  mov         eax,0CCCCCCCCh  
00892599  rep stos    dword ptr es:[edi]  //将main初始函数栈帧全部初始化为0CCCCCCCCh

在这里插入图片描述

0089259B  mov         ecx,89C003h  
008925A0  call        0089130C  //进入main函数
	int a = 2;
008925A5  mov         dword ptr [ebp-8],2  //ebp - 8就是a的位置,将a赋值为2
	int b = 6;
008925AC  mov         dword ptr [ebp-14h],6  //同理ebp - 14h为b的地址将b赋值为6
	int c = 0;
008925B3  mov         dword ptr [ebp-20h],0  //ebp - 20h为c的地址,c赋值为0

在这里插入图片描述

	c = add(a, b);
008925BA  mov         eax,dword ptr [ebp-14h] //传参,将b值传给add函数 ,先将b值传给eax
008925BD  push        eax  //eax压栈
008925BE  mov         ecx,dword ptr [ebp-8]  //传参,将a值传给add函数,先将a值传给ecx
008925C1  push        ecx  //ecx压栈

在这里插入图片描述

008925C2  call        00891023  //进入add
//带符号:008925C2  call        _add (0891023h) 

int add(int a, int b)
{
008917B0  push        ebp  //记录上一个ebp的地址
008917B1  mov         ebp,esp  //将ebp赋值成esp地址
008917B3  sub         esp,0CCh  //add函数栈帧
008917B9  push        ebx  
008917BA  push        esi  
008917BB  push        edi  
008917BC  lea         edi,[ebp-0Ch]  
008917BF  mov         ecx,3  
008917C4  mov         eax,0CCCCCCCCh  
008917C9  rep stos    dword ptr es:[edi]  //与main函数栈帧初始化同理,将add函数初始化为CC CC CC CC
008917CB  mov         ecx,offset _18BA86EA_test@c (089C003h)  
008917D0  call        @__CheckForDebuggerJustMyCode@4 (089130Ch)  
//008917C9  rep stos    dword ptr es:[edi]  
//008917CB  mov         ecx,89C003h  
//008917D0  call        0089130C  
//	int d = a + b;
//008917D5  mov         eax,dword ptr [a]  
//008917D8  add         eax,dword ptr [b]  
//008917DB  mov         dword ptr [d],eax  
//	return d;
//008917DE  mov         eax,dword ptr [d]  
	int d = a + b;
008917D5  mov         eax,dword ptr [ebp+8]  //将a赋值给eax
008917D8  add         eax,dword ptr [ebp+0Ch]  //将eax加上b,即2+6 = 8
008917DB  mov         dword ptr [ebp-8],eax  //将eax=8赋值给d
	return d;
008917DE  mov         eax,dword ptr [ebp-8]  //将d的值赋值给寄存器eax
}

在这里插入图片描述

008917E1  pop         edi  //出栈edi
008917E2  pop         esi  //出栈esi
008917E3  pop         ebx  //出栈ebx
008917E4  add         esp,0CCh  //将add函数销毁,esp回到ebp的位置
008917EA  cmp         ebp,esp  
008917EC  call        00891235  //回到main
008917F1  mov         esp,ebp  //将ebp的地址给esp
008917F3  pop         ebp  //出栈ebp,让ebp指向上一次地址位置
008917F4  ret  
008925C7  add         esp,8 // 销毁两个形参,esp指向main函数栈顶
008925CA  mov         dword ptr [ebp-20h],eax  //将eax(返回)值8赋值给ebp - 20h 也就是c

在这里插入图片描述
在这里插入图片描述

	printf("%d\n",c);
008925CD  mov         eax,dword ptr [ebp-20h]  //将c值赋给eax
008925D0  push        eax  
008925D1  push        897BCCh  
008925D6  call        008913A2  
008925DB  add         esp,8  
	return 0;
008925DE  xor         eax,eax  
}
//和add函数销毁一样,main函数销毁,结束程序
008925E0  pop         edi  
008925E1  pop         esi  
008925E2  pop         ebx  
008925E3  add         esp,0E4h  
008925E9  cmp         ebp,esp  
008925EB  call        00891235  
008925F0  mov         esp,ebp  
008925F2  pop         ebp  
008925F3  ret  
本篇文章如有错误,还请大佬指点!后续会慢慢优化!
  • 21
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

未见花闻

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

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

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

打赏作者

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

抵扣说明:

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

余额充值