c语言的函数栈帧究竟是什么?你知道吗?(代码片段)

未见花闻 未见花闻     2022-12-18     375

关键词:


前面的话:

作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!
博主的码云gitee,平常博主写的程序代码都在里面。

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 + b;
	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  
本篇文章如有错误,还请大佬指点!后续会慢慢优化!

函数栈帧的创建与销毁,带你了解代码底层原理(代码片段)

在学习C语言时,我们难免有许多疑问例如:(1)局部变量是怎么创建的?(2)为什么局部变量的值是随机的?(3)函数是怎么传参的?传参的顺序如何?(4)形参和实参是什么关系?(5)函数调用是怎么做的࿱... 查看详情

问卷调查(代码片段)

...最大的困难就是自己吧,会经常拖延。2.1你是怎么学习C语言的?(作业,实验,教材,其他),目前为止估算自己写过多少行代码?看MOOC,学校发的教材。不记得。2.2学了C语言,你分的清数组指针,指针数组;函数指针,指针... 查看详情

学好c语言,还需要掌握这个内功——函数栈帧的创建与销毁(代码片段)

学习本篇文章之前,你或许还有这些疑问:局部变量是怎么创建的?为什么局部变量的值是随机值?函数是怎么传参的?传参的顺序是什么?形参和实参是什么关系?函数调用结束后怎么返回?看... 查看详情

调查问卷(代码片段)

...什么困难?A:专业方面没有什么困难。Q:2.1 你是怎么学习C语言的?(作业,实验,教材,其他),目前为止估算自己写过多少行代码?A:听课,看教程,实践,Google,百度。大约有1000行左右。Q:2.2 学了C语言,你分的清数组指针... 查看详情

调查问卷(代码片段)

...业学习方面情况如何,有什么收获,是否有什么困难?C语言还不是很熟练很多细节方面的东西没有做好2.1你是怎么学习C语言的?(作业,实验,教材,其他),目前为止估算自己写过多少行代码?课本,网上查阅资料,实践2.2... 查看详情

c语言学习--函数栈帧的创建和销毁(代码片段)

前言在学习C语言的过程中,大家是否会存在一些困惑?比如:局部变量是如何创建的?为什么说局部变量未初始化时,其中存储的时随机值?函数到底时如何传参的?实参传递的顺序又是怎样的?... 查看详情

深入理解c语言从函数栈帧角度理解return关键字(代码片段)

初识函数栈帧如上图可见,函数在被调用的时候会现在栈上开辟一个空间,我们称之为栈帧,之后函数内部的变量在这块区域进行空间开辟。但是函数在调用的时候,怎么知道需要开辟多大空间呢??࿱... 查看详情

问卷调查(代码片段)

...终不能超过老师的脚步,一直在跟行。2.1你是怎么学习C语言的?(作业,实验,教材,其他),目前为止估算自己写过多少行代码?作业,实验和教材示例都有。大一上学期总和应该在5000-7000附近2.2学了C语言,你分的清数组指... 查看详情

c语言进阶顶级神功!函数栈帧的创建和销毁(代码片段)

本章目录温馨提示开篇介绍学前疑惑学前准备1.环境选择2.知识铺垫正文开始1.大致轮廓了解(源代码及反汇编)2.函数栈帧的创建和销毁总流程3.函数栈帧的创建3.1main函数的创建(分解)3.2Add函数的创建(分解... 查看详情

栈帧(代码片段)

C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。首先,栈是从高地址向低地址延伸的。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶... 查看详情

go语言必须支持多返回值函数,你知道为什么吗?(代码片段)

        大多数编程语言的函数(方法)都只能返回一个值,这种函数也是在数学中的标准定义,如y=f(x),后面的f(x)不管多复杂,y永远只有一个。不过有少数编程语言,函数可以返回多个值࿰... 查看详情

go语言必须支持多返回值函数,你知道为什么吗?(代码片段)

        大多数编程语言的函数(方法)都只能返回一个值,这种函数也是在数学中的标准定义,如y=f(x),后面的f(x)不管多复杂,y永远只有一个。不过有少数编程语言,函数可以返回多个值࿰... 查看详情

问卷调查

...如何,有什么收获,是否有什么困难?答:收获了一些c语言的代码知识,有些知识需要反复咀嚼不太好轻松消化2.1你是怎么学习C语言的?(作业,实验,教材,其他),目前为止估算自己写过多少行代码?答:作业,不记得了2... 查看详情

c语言之函数调用及栈帧分析(代码片段)

一.前言 每一次函数调用都是一个过程。这个过程我们通常称之为:函数的调用过程。这个过程要为函数开辟栈空间,用于本次函数的调用中临时变量的保存,现场保护。这块栈空间就是函数栈帧。实验代码:#inc... 查看详情

函数栈帧的创建与销毁(代码片段)

栈帧的创建与销毁什么是栈帧C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。首先应该明白,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一... 查看详情

4.27

...有什么困难?学会了helloworld的写法,没有2.1你是怎么学习C语言的?(作业,实验,教材,其他),目前为止估算自己写过多少行代码?作业,1002.2学了C语言,你分的清数组指针,指针数组;函数指针,指针函数这些概念吗?分不... 查看详情

调查问卷(代码片段)

...自己是如何投入这个专业的学习的?答:想着自己能精通语言然后能开发出一个游戏。1.4结合过去的一学期,你目前在专业学习方面情况如何,有什么收获,是否有什么困难?答:收货的话还行,逻辑思维稍许变强了些吧,困难... 查看详情

问卷调查

...否有什么困难?收获很多,困难也很多2.1你是怎么学习C语言的?(作业,实验,教材,其他),目前为止估算自己写过多少行代码?课堂学习2.2学了C语言,你分的清数组指针,指针数组;函数指针,指针函数这些概念吗?不2.3... 查看详情