【详解】函数栈帧——多图(c语言)

article/2025/10/18 22:30:40

目录

前言

一.函数栈帧是什么?

二、栈帧准备知识

1.内存分区

2.什么是栈?

 3.esp,ebp,eax寄存器

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

调用函数之前:

将传入函数的值放入栈中

函数执行:

1.保护当前ebp

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

 3.保存局部变量

 4.参数运算

 函数返回:

1.存储返回值

 2.销毁空间

 3.ebp回上一栈帧栈底

 4.销毁形参

 5.main函数拿到返回值

总结

写在最后


前言

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


一.函数栈帧是什么?

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

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

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

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

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


二、栈帧准备知识

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

1.内存分区

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

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

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

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

 

2.什么是栈?

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

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

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

 压栈(push):

 出栈(pop):

 

 3.esp,ebp,eax寄存器

ebpebp是基址指针,保存调用者函数的地址,总是指向当前栈帧栈底
espesp是被调函数指针,总指向函数栈栈顶
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语言的函数只能有一个返回值

  

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


📝总结

经过以上的学习,我希望大家能学到一下几点:

什么是函数栈帧?

函数在调用时发生了什么?

为什么函数只能有一个返回值?

为什么形参的改变无法影响实参?

答案都在文中哦!😘 


✨写在最后

在自己学习函数栈帧的创建与销毁时,我看了很多博客,大家都比较喜欢直接通过汇编代码来讲,我觉得这对于像我这样c语言初学者,以及在此之前根本没接触过汇编的人简直是折磨与煎熬,难以理解发生了什么。因此我决定直接以图形的直观方式为大家讲解,虽然相比于实际情况我省略了些许步骤,但那并不影响我们了解栈帧的创建与销毁。

对于函数栈帧,我认为并不需要专门去学习相关的汇编知识,只需要作为了解知识,并且能明白我上面提出的四个问题就够了


http://chatgpt.dhexx.cn/article/HvCxh29I.shtml

相关文章

【mcuclub】模数转换ADC0832

一、实物图 二、原理图 编号名称功能1CS片选使能&#xff0c;低电平芯片使能。2CH0模拟输入通道0&#xff0c;或作为IN/-使用。3CH1模拟输入通道1&#xff0c;或作为IN/-使用。4GND电源地5DI数据信号输入&#xff0c;选择通道控制。6DO数据信号输出&#xff0c;转换数据输出。7…

[技术讨论] [DDS] AD9833原理介绍及chiliDDS驱动分享(上)

​ 其实本文还有另一标题&#xff1a;《AD9833调不通&#xff1f;看这篇**就够了》 总觉字里行间隐隐霸气外露&#xff0c;不符合作者低调的风格&#xff0c;于是换了个朴素标题。标题狂不狂暂且不评&#xff0c;作者水平有限却是个事实&#xff1b;看到这篇**是你我缘分&…

ADC0832的使用

百度搜索ADC0832 Datasheet便可以免费获取该芯片的权威数据手册。 最重要的是查看它的时序及对应英文词组的意思。 CLK为时钟信号&#xff0c;需要外部输入&#xff0c;可直接与单片机引脚相连 Chip Select&#xff08;CS&#xff09;:从Timing图中可以看出芯片工作期间要保持…

ADC0832的AD模数转换原理及编程

✅作者简介&#xff1a;嵌入式领域优质创作者&#xff0c;博客专家 ✨个人主页&#xff1a;咸鱼弟 &#x1f525;系列专栏&#xff1a;单片机设计专栏 目录 一、描述 二、模数转换原理&#xff1a; 三、模数转换的过程&#xff1a; 四、八位串行A/D转换器ADC0832简介&…

ADC0809的使用

一、前言介绍 使用ADC0809对一个模拟电压进行转换转换后的电压使用数码管显示出来 二、ADC0809的介绍 1、ADC0809简介 ADC0809是采用COMS工艺制造的双列直插式单片8位A/D转换器。分辨率8位&#xff0c;精度7位&#xff0c;带8个模拟量输入通道&#xff0c;有通道地址译码锁…

基于STM32F103RCT6的AD9833驱动开发(代码可以免费发邮箱)

基于STM32F103RCT6的AD9833驱动开发&#xff08;代码可以免费发邮箱&#xff09; AD9833手册分析 管脚定义&#xff1a; 手册就先讲到这里&#xff0c;不明白的欢迎评论区留言&#xff0c;另外我会把代码还有手册一并发送给感兴趣的朋友。 AD9833典型应用电路&#x…

AD9833数字信号发生器模块

简 介&#xff1a; 本文记录了使用快速制版测试AD9833这款数字信号发生器的内容。 关键词&#xff1a; AD9388&#xff0c;数字信号发生&#xff0c;快速制版 基于AD9833的正弦波&#xff0c;三角波&#xff0c;方波频率发生模块可以通过ZIGBEE来完成输入输出控制。其中还包括有…

STM32驱动AD9833模块

STM32驱动AD9833模块 前言软硬件准备一、本次使用的硬件二、代码 链接 前言 淘宝上买了个AD9833模块&#xff0c;stm32用商家的例程代码可以调频&#xff0c;可以调相&#xff0c;就是调不了幅度。换了几块不同32开发板都不行&#xff0c;重新以正点原子F103的工程为基础把驱动…

AD9833信号波形谐波

AD9833产生高频信号的谐波 ~ AD9833是一款AnalogDevices公司提供的数字信号可编程信号发生器芯片。它一般配有外置的主时钟信号&#xff0c;每次时钟信号将将内部28位的相位累加器递增一个相位数值。该相位数值由芯片SPI串口被外部的MCU设置。 相位累加器的高12位选择内部4096…

51驱动AD9833

使用51驱动AD9833模块的使用 关于AD9833相关参数程序流程代码片上传程序总结 原文链接&#xff1a;https://www.yourcee.com/newsinfo/2925703.html 关于AD9833 AD9833是一款低功耗、可编程波形发生器&#xff0c;能够产生正弦波、三角波和方波输出。各种类型的检测、信号激励…

Arduino + AD9833 波形发生器

Arduino SI5351 方波发生器_姜戈12的博客-CSDN博客SI5351 方波发生器https://blog.csdn.net/jiangge12/article/details/125815044 感觉 Si5351 只有方波还是少点意思。 看到有人做 AD9833 &#xff0c;成品卖355元。https://www.bilibili.com/video/av463721457/ 上面视频…

[技术讨论][DDS] AD9833原理介绍及chiliDDS驱动分享(上)

其实本文还有另一标题&#xff1a;《AD9833调不通&#xff1f;看这篇**就够了》 总觉字里行间隐隐霸气外露&#xff0c;不符合作者低调的风格&#xff0c;于是换了个朴素标题。标题狂不狂暂且不评&#xff0c;作者水平有限却是个事实&#xff1b;看到这篇**是你我缘分&#xff…

STM32单片机TFT显示AD9833 DDS信号发生器语音播报正弦波方波三角波

实践制作DIY- GC0146---TFT显示AD9833 DDS信号发生器 基于STM32单片机设计---TFT显示AD9833 DDS信号发生器 二、功能介绍&#xff1a; 硬件组成&#xff1a;STM32F103C系列最小系统板 1.8寸TFT彩屏AD9833信号模块4*4矩阵键盘DY-SV17F语音播报模块 1.通过4*4键盘来设定频率值和…

失真很大的波形发生器AD9833

波形发生器AD9833 01 波形发生器 一、AD9833 AD9833是一款AnalogDevices公司提供的数字信号可编程信号发生器芯片。 它一般配有外置的主时钟信号&#xff0c;每次时钟信号将将内部28位的相位累加器递增一个相位数值。 该相位数值由芯片SPI串口被外部的MCU设置。  相位累加器…

【STM32+cubemx】0030 HAL库开发:DDS芯片AD9833实现简单的波形发生器

大家好&#xff0c;我是学电子的小白白&#xff0c;今天带大家了解一款波形发生器芯片——AD9833。 AD9833是AD公司出品的一款DDS波形发生器&#xff0c;能够产生正弦波、三角波和方波输出。 1&#xff09;什么是DDS 通俗来讲&#xff0c;DDS是一种把波形预先存储在芯片内部的…

2.电赛进行时......(AD9833(DDS)模块的学习使用)

文章目录 前言一、DDS是什么&#xff08;信号发生器&#xff09;&#xff1f;二、AD9833模块介绍1.概述2.模块移植3、效果展示![在这里插入图片描述](https://img-blog.csdnimg.cn/2b0d29939275403f9248339c894965a0.png#pic_center) 前言 如果是玫瑰&#xff0c;它总会开花的…

AD9833介绍与应用(C语言实现)

1.特性 低功耗、可编程波形发生器&#xff0c;能产生正弦波、三角波和方波的输出&#xff1b; 主频时钟为25MHz时&#xff0c;可实现0.1Hz的分辨率&#xff1b;主频时钟为1MHz时&#xff0c;可实现0.004Hz的分辨率&#xff1b; 三线式串行接口(SDATA、SCLK、FSYNC)写入数据&a…

2021电赛预测—手把手带你玩转DDS模块AD9833

1、电赛预测 摘要&#xff1a;原定于7月28日才发布的器件清单&#xff0c;提前2天在26号就发布。感觉大家现在应该猜题预测&#xff0c;与其猜题&#xff0c;还不如静下心来做题。盲目猜题是没有必要的&#xff0c;熟悉相关器件倒尤为重要。不要到处去水群聊天&#xff0c;听风…

幻方....

c语言上机。。。。 c写的幻方。 1 /*************************************************************************2 > File Name: code/class/7.c3 > Author: 111qqz4 > Email: rkz2013126.com 5 > Created Time: 2015年11月11日 星期三 19时31分5…

Unity脚本生命周期流程图

转载自【无幻】&#xff1a;http://blog.csdn.net/akof1314/article/details/39323081 渲染 OnPreCull: 在相机剔除场景之前调用此函数。相机可见的对象取决于剔除。OnPreCull 函数调用发生在剔除之前。 OnBecameVisible/OnBecameInvisible: 在对象对于相机可见/不可见时调用此…