C语言超详细讲解函数栈帧的创建和销毁

目录
  • 1、本节目标
  • 2、相关寄存器
  • 3、相关汇编指令
  • 4、什么是函数栈帧
  • 5、什么是调用堆栈
  • 6、函数栈帧的创建和销毁
    • (1)、main函数栈帧的创建与初始化
    • (2)、main函数的核心代码
    • (3)、Add函数的调用过程
    • (4)、Add函数栈帧的销毁
    • (5)、调用完成
  • 7、对开篇问题的解答

1、本节目标

C语言绝命七连问,你能回答出几个?

局部变量是如何创建的?为什么局部变量不初始化其内容是随机的?有些时候屏幕上输出的"烫烫烫"是怎么来的?函数调用时参数时如何传递的?传参的顺序是怎样的?函数的形参和实参的关系是什么?函数的返回值是如何带回的?函数是怎样在栈区上开辟和释放空间的?

想要对上面的这六个问题做出准确深入的回答,我们需要学习函数栈帧的创建和销毁相关知识,在正式进入函数栈帧之前,我们还需要了解一些相关的寄存器和汇编指令。

2、相关寄存器

eax:通用寄存器,保留临时数据,常用于返回值。ebx:通用寄存器,保留临时数据。ebp:栈底寄存器,用来记录栈底的地址。esp:栈顶寄存器,用来记录栈顶的地址。eip:指令寄存器,保存当前指令的下一条指令的地址。

3、相关汇编指令

mov:数据转移指令。sub:减法命令。add:加法命令。push:数据入栈,同时esp栈顶寄存器也要发生改变。pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变。call:函数调用,1. 压入返回地址 2. 转入目标函数。jump:通过修改eip,转入目标函数,进行调用。lea:传递地址指令,用于加载有效地址。ret:恢复返回地址,压入eip,类似pop eip命令。

4、什么是函数栈帧

函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放:

函数参数和函数返回值。临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)。保存上下文信息(包括在函数调用前后需要保持不变的寄存器)。

同时,每一次函数调用,编译器都会为该函数分配一块空间,而这块空间就被称为这个函数的函数栈帧;并且,这块空间是由两个寄存器来维护的:esp寄存器(记录栈顶的地址)和ebp寄存器(记录栈底的地址)。

5、什么是调用堆栈

函数调用堆栈是反馈函数调用逻辑的。我们以main函数的调用为例:

我们可以看到,mainCRTStartup调用__scrt_common_main,__scrt_common_main调用__scrt_common_main_seh,__scrt_common_main_seh调用_SCRT_STARTUP_MAIN,_SCRT_STARTUP_MAINmain,main调用Add。

6、函数栈帧的创建和销毁

我们以一段程序为例讲解函数栈帧:(注意: 函数栈帧的创建和销毁过程,在不同的编译器上实现的方法和细节会有所差异,一般来说,越新的编译器对函数栈帧的封装就越严密,本次演示以VS2019为例。)

演示代码

#include<stdio.h>int Add(int x, int y){int z = 0;z = x + y;return z;}int main(){int a = 3;int b = 5;int ret = 0;ret = Add(a, b);printf("%d", ret);return 0;}

(1)、main函数栈帧的创建与初始化

(2)、main函数的核心代码

(3)、Add函数的调用过程

F11进入Add函数内部,观察Add函数的反汇编代码

代码执行到Add函数的时候,就要开始创建Add函数的栈帧空间了。

在Add函数中创建栈帧的方法和在main函数中是相似的,在栈帧空间的大小上略有差异而已。

1. 将main函数的 ebp 压栈。

2. 计算新的 ebp 和 esp。

3. 将 ebx , esi , edi 寄存器的值保存。

4. 计算求和,在计算求和的时候,我们是通过 ebp 中的地址进行偏移访问 到了函数调用前压栈进去的参数,这就是形参访问。

5. 将求出的和放在 eax 寄存器中准备带回。

(4)、Add函数栈帧的销毁

当函数调用要结束返回的时候,前面创建的函数栈帧也开始销毁,具体销毁过程如下:

(5)、调用完成

调用完Add函数,回到main函数的时候,继续往下执行,可以看到:

00BE185D add esp,8 esp直接+8,相当于跳过了main函数中压栈的a’和b’。

00BE1860 mov dword ptr [ebp-20h],eax 将eax中值,存档到ebp-0x20的地址处,其实就是存储到main函数中ret变量中,而此时eax中就是Add函数中计算的x和y的和,可以看出来,本次函数的返回值是由eax寄存器带回来的。程序是在函数调用返回之后,在eax中去读取返回值的。

7、对开篇问题的解答

当我们完整的了解了函数栈帧创建和销毁的过程后,我们就可以回答开篇提到的问题了:

局部变量是如何创建的?

局部变量的创建是在局部变量所在的函数的栈帧创建完成并初始化后,然后在该栈帧内为局部变量分配空间的。

为什么局部变量不初始化其内容是随机的?

因为编译器在创建函数栈帧后会在栈帧空间里面放入一个值,而这个值是随机的。

有些时候屏幕上输出的"烫烫烫"是怎么来的?

因为main函数调用时,在栈区开辟的空间的其中每一个字节都被初始化为0xCC,而如果我们定义的是一个未初始化的数组,且这个数组恰好在这块空间上创建,因为0xCCCC(两个连续排列的0xCC)的汉字编码是“烫”,所以屏幕上输出的就是烫烫烫。

函数调用时参数时如何传递的?传参的顺序是怎样的?

我们在调用函数之前,就会在栈顶上从右向左依次压入需要传递的参数,在创建好被调函数的函数栈帧后通过指针的偏移量来使用传递过去的参数,而不是在被调函数的函数栈帧内创建形参。

函数的形参和实参的关系是什么?

形参是实参的一份临时拷贝,二者的存储位置不同,形参的改变不会影响实参。

函数的返回值是如何带回的?

函数的返回值通过eax寄存器带回。

函数是怎样在栈区上开辟和释放空间的?

函数通过改变esp和edp的指向来创建和销毁空间,空间销毁并不会清除该空间中的数据,下一次使用该空间时新数据直接覆盖原数据即可。

到此这篇关于C语言超详细讲解函数栈帧的创建和销毁的文章就介绍到这了,更多相关C语言函数栈帧内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言函数栈帧的创建和销毁详解

    目录 写在前面 Add函数的调用 函数传参 Add函数栈帧的创建 Add函数栈帧的销毁 main函数栈帧的销毁 总结 写在前面 我们知道,每一次函数调用都需要在栈区上为其开辟一块空间,这块空间就叫做这个函数的栈帧. 而栈是从高地址向低地址延伸的.每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息.寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址). 这样我们就了解了寄存器ebp和寄存器esp中存放的是地址,这两个地址是用来维护函数栈

  • C语言中函数栈帧的创建和销毁的深层分析

    目录 一.本文目标 二.基础知识 1.寄存器 2.代码案例 3.总体栈帧概况 4.所需反汇编代码总览 三.函数栈帧创建销毁过程 1._tmainCRTStartup函数(调用main函数)栈帧的创建 2.main函数栈帧的创建 3.main函数内执行有效代码(变量) 4.Add函数栈帧的创建 5.Add函数内执行有效代码 6.Add函数栈帧的销毁 7.main函数栈帧的销毁 四.总结 一.本文目标 1.局部变量是怎么创建的? 2.为什么局部变量的值是随机值? 3.函数是怎么传参的?传参的顺序是怎

  • C语言函数栈帧的创建与销毁详解

    目录 前言 一.函数栈帧是什么? 1.寄存器 2.ebp与esp 二.函数栈帧的创建 1.代码块 2.调用堆栈 3.esp与ebp如何维护栈帧 总结 前言 大家在学习的时候一定有以下困惑: 局部变量是怎么创建的?为什么局部变量的值是随机值?函数是怎么传参?传参的顺序是怎样的?形参与实参是什么关系?函数调用是怎么做到的?函数调用完成不是销毁了吗,如何带回的返回值? 以上这些都可以通过了解函数栈帧的创建与销毁来理解.接下来我就带大家来了解函数栈帧的创建与销毁. 本次使用的编辑器是VS2013,因为越

  • C语言函数栈帧的创建和销毁介绍

    在初学c语言中,很多时候要记的内容有点多,有时候并不能深入的了解它.关于函数的栈帧可以帮助我们深入了解函数传参的过程,让我们了解c语言. 以下是我们平时接触过,但不了解的问题: 1.为什么局部变量在未赋值前是随机的. 2.局部变量创建的过程. 3.函数传参,传参的顺序问题. 4.形参与实参的关系什么. 5.调用函数是怎么调用的,调用的过程是什么. 6.调用函数结束后,是怎样返回的. 这些问题我们在学校可能并不会接触,也不会出现在考试的试卷上,但是作为计算机专业的学生,做一些认识和了解是很有必要的

  • c语言函数栈帧的创建和销毁过程详解

    目录 1 相关知识介绍 1.1 寄存器 1.2 函数栈帧概述 2 栈帧创建与销毁过程 1 相关知识介绍 1.1 寄存器 一般计算机内通用寄存器包括eax,ebx,ecx,edx,esi,edi,esp,edp,其中esp,ebp这两个寄存器是用来存放地址的,这两个地址就是用来维护函数栈帧的 1.2 函数栈帧概述 我们知道c语言中函数都是被调用的,main函数里面能调用其他函数,其实main函数也是被别的函数调用的.main函数是在 _tmainCRTSartup 函数中被调用的,_tmainCR

  • C语言超详细讲解函数栈帧的创建和销毁

    目录 1.本节目标 2.相关寄存器 3.相关汇编指令 4.什么是函数栈帧 5.什么是调用堆栈 6.函数栈帧的创建和销毁 (1).main函数栈帧的创建与初始化 (2).main函数的核心代码 (3).Add函数的调用过程 (4).Add函数栈帧的销毁 (5).调用完成 7.对开篇问题的解答 1.本节目标 C语言绝命七连问,你能回答出几个? 局部变量是如何创建的?为什么局部变量不初始化其内容是随机的?有些时候屏幕上输出的"烫烫烫"是怎么来的?函数调用时参数时如何传递的?传参的顺序是怎样的

  • C语言超详细讲解函数指针的运用

    目录 前言 计算器的例子 回调函数 转移表 前言 前面我们学习了各种各样的指针类型,有些指针可以说是稀奇百怪,特别是函数指针,有些朋友可能觉得,函数指针有些多余,调用函数为什么要用指针调用,直接调用不好吗? 接下来我们从具体的实例来回答同学们的问题,加深对函数指针的理解. 计算器的例子 接下来我们写一个简单的计算器程序,完成不同的计算功能比如加减乘除: #include <stdio.h> //相加函数 int add(int a, int b) { return a + b; } //相减函

  • C语言详尽图解函数栈帧的创建和销毁实现

    目录 常见寄存器 基本的汇编语言知识 具体实现 关于栈帧创建与销毁的问答题 注:本文章所使用的编译器是VS2010,由于不同编译器的函数栈帧与销毁略有差异,所以具体细节请读者自行实践! 常见寄存器 寄存器有:eax.ebx.ecx.edx.edi.esi.ebp.esp 其中 ebp 和 esp 是用来维护函数栈帧的,他们里面存放的是地址. 他们维护的是某个正在被调用的函数.通常我们又称 ebp 为栈底指针,称 esp 为栈顶指针 基本的汇编语言知识 push:压栈 pop:出栈 mov:若有变

  • C语言函数栈帧的创建与销毁原理图解

    目录 什么是函数栈帧 什么是栈? 与函数栈帧有关的汇编语句 函数如何创建栈帧并销毁 main函数栈帧开辟 调用Add函数 返回主函数 什么是函数栈帧 我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的. 那函数是如何调用的?函数的返回值又是如何待会的?函数参数是如何传递的?这些问题都和函数栈帧有关系. 函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放: 函数参数和函数返回值 临时变量

  • C语言超详细讲解栈的实现及代码

    目录 前言 栈的概念 栈的结构 栈的实现 创建栈结构 初始化栈 销毁栈 入栈 出栈 获取栈顶元素 获取栈中有效元素个数 检测栈是否为空 总代码 Stack.h 文件 Stack.c 文件 Test.c 文件 前言 栈的概念 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作.进行数据插入和删除操作的一端称为栈顶,另一端称为栈底.栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则.有点类似于手枪弹夹,后压进去的子弹总是最先打出,除非枪坏了. 压栈:栈的插入

随机推荐