我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C语言函数栈帧详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C语言函数栈帧详解

前言

在c语言中我们会将一些功能单独写成一个函数,以供主函数调用,在表面来看调用的过程就是写出一个函数后,只需要在调用时中通过函数名将实参传给形参就实现了整个过程,但实际上调用的过程远比你想的复杂,这其中函数栈帧起着关键作用。通过本篇文章,我将告诉你函数在调用时计算机内究竟发生了什么?

一.函数栈帧是什么?

C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。(来自百度百科)。

通过这句话我们可以提炼出两个关键信息:

1.每个未运行完的函数都有一个对应的栈帧

2.栈帧保存了函数的返回地址和局部变量

先对栈帧有一个简单的概念,知道其主要作用是什么就行。

二、栈帧准备知识

由于函数栈帧不光涉及c语言代码知识,如果是小白一定要认真看本节,这对帮助你理解栈帧非常重要,也不要因为发现这些知识你之前完全没听说过就产生畏难心理,事实上,我们只需要掌握期其中一些非常关键的地方就足够了。

1.内存分区

内存中主要分为栈区,堆区,静态区,以及其他部分。

栈区:由高地址往低地址增长,主要用来存放局部变量,函数调用开辟的空间,与堆共享一段空间。(本篇重点)

堆区:由地地址向高地址增长,动态开辟的空间就在这里(malloc,realloc,calloc,free),与栈共享一段空间。

静态区:主要存放全局变量和静态变量。

2.什么是栈?

前面已经知道栈中存放了函数调用开辟的空间即栈帧,因此我们要明白什么是栈帧,必须先知道什么是栈。

栈是一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后放入的数据被最先读出来)。

简单来讲你可以把栈理解为一个弹夹,而我们放的数据就像子弹,当我们射子弹时,总是会把后压入的弹先射出去,因为后压入的弹一定是放在最上面的,而先压入的弹后射出去,因为先压入的弹在最下面。这就是栈最大的特点"先入后出,后入先出",而往栈中放数据我们称作压栈(push),拿出栈中的数据我们叫出栈(pop)。

压栈(push):

出栈(pop):

3.esp,ebp,eax寄存器

ebp ebp是基址指针,保存调用者函数的地址,总是指向当前栈帧栈底
esp esp是被调函数指针,总指向函数栈栈顶
eax 累加器,用来乘除法,与函数返回值(本篇主要关注第二个功能)

简单来讲就是esp和ebp是两个指针,ebp指向当前栈帧栈底,esp指向函数栈栈顶。

能看到,ebp并不是指向整个函数栈的栈底,而是指向当前栈帧的栈底,而由于esp总是指向栈顶,且栈只允许一个方向的操作,因此esp指向其实也是当前栈帧的栈顶,不过当前栈帧的栈顶始终与栈顶相同,因此说esp指向的是栈顶。

三、详解栈帧创建与销毁全过程

有了以上知识就能够初步理解栈帧从创建到销毁的全过程了,接下来我会一步一步讲解

假设我们有当前代码:


#include<stdio.h>
int add(int a, int b)
{
	int c = 0;
	c = a + b;
	return c;
}
int main()
{
	int a = 1;
	int b = 1;
	int sum;
	sum = add(a, b);
	return 0;
}

调用函数之前:

此时我们准备执行函数调用"sum = add(a,b);"此时栈中如下:

将传入函数的值放入栈中

由于函数调用涉及到传参,因此我们在调用函数之前,需要先将传入的参数保存,以方便函数的调用,因此需要将add函数的a=1,b=2,push入栈保存

函数执行:

1.保护当前ebp

由于我们马上要创建新的栈帧空间,因此ebp和esp都得将变动,为了能够让我们调用完add函数后还能让ebp回到当前位置我们需要对ebp的值进行保护,即将此时ebp的值压入栈(至于为什么不需要保护esp,看到后面你就能明白)

2.创建所需调用函数的栈帧空间

令ebp指向当前esp的位置并根据add函数的参数个数,创建一个大小合适的空间。

① ebp指向当前esp的位置

②创建空间

3.保存局部变量

将add函数中创建的变量"int c = 0"放入刚刚开辟的栈帧空间中

4.参数运算

根据形参与局部变量,进行对应的运算,这里执行"c = a +b", 得到 c = 2,放入刚才c对应的位置。

到次函数执行就完成了,接下来就要开始实现函数返回

函数返回:

1.存储返回值

现在我们已经达成了目的"add(a,b)",要将之前创建的add的函数栈销毁,以使得我们能够回到main函数中正常执行,而在销毁add的函数栈帧前我们的main函数可还没有拿到运算结果,因此我们需要先将需要返回的值存储起来,存储的位置就是前面提到的eax寄存器,这里"return c",我们将c的值放到eax寄存器中。

2.销毁空间

拿到了运算结果后,我们就没有任何任何顾虑了,可以直接销毁函数的栈桢空间了。

3.ebp回上一栈帧栈底

此时ebp拿到之间存储的上一栈帧栈底的值,回到相应的位置,于此同时,存储的ebp没有用了,也将被销毁。

4.销毁形参

形参也不再有用,因此也随即销毁。(这里也让我们明白:由于形参在调用完函数后就会销毁,且与实参根本不是同一地址,因此形参的改变无法影响实参。)

5.main函数拿到返回值

在讲解main函数怎么拿到返回值前,我想先问一个问题:

上图中所谓的前一栈帧指的是什么?

大家都知道,我们编写的c程序都是从一个main函数开始的,实际上,代码并不是直接从main函数开始运行的,main函数的本质也是一个被其他代码调用的函数,至于被谁调用,这里就不展开讲解了,这里提出这个问题是想要大家知道:

main函数是一个函数,它有自己的栈帧。

因此所谓的前一栈帧实际上就是调用add函数的main函数的栈帧。

因此我们要让main函数拿到返回值,只需要把eax寄存器中的值放入main栈帧中sum对应的位置就行。(这里也能让我们明白:由于我们只有一个eax寄存器,因此c语言的函数只能有一个返回值。)

至此栈帧的创建与销毁结束,函数调用完成。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程网的更多内容!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C语言函数栈帧详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

怎么理解C语言的函数栈帧

本篇内容介绍了“怎么理解C语言的函数栈帧”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、函数栈帧的创建1.寄存器一般来说,计算机中的寄存器
2023-06-25

C语言函数栈帧如何创建和销毁

这篇文章主要为大家展示了“C语言函数栈帧如何创建和销毁”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C语言函数栈帧如何创建和销毁”这篇文章吧。写在前面我们知道,每一次函数调用都需要在栈区上为其开
2023-06-29

C++函数栈帧的示例分析

这篇文章将为大家详细讲解有关C++函数栈帧的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一、什么是函数栈帧每一次函数调用都是一个过程,为函数开辟栈空间,用于本次函数调用中临时变量的保存、现场保护
2023-06-20

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录