目录

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

1、本节目标

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

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

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


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函数的时候,继续往下执行,可以看到:

add esp,8: esp直接+8,相当于pop了main函数之前压栈的a’和b’。

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


7、对开篇问题的解答

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

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

  2. 为什么局部变量不初始化其内容是随机的?
    因为函数栈帧在创建完成之后,编译器会把该栈帧空间的内容全部初始化为一个值,而这个值是随机的,且在不同编译器下该值可能是不同的。(VS下该随机值为0xcc cc cc cc)

  3. 有些时候屏幕上输出的"烫烫烫"是怎么来的?
    函数的栈帧创建之后,其空间中的每一个字节都被初始化为一个随机值,如果这个随机值为 0xcc (比如VS下),且如果我们定义的是一个未初始化的数组,而这个数组恰好在这块空间上创建,那么打印此数组的内容时屏幕上输出的就是烫烫烫 。(因为0xCCCC(两个连续排列的0xCC)的汉字编码是“烫”)

  4. 函数调用时参数时如何传递的?传参的顺序是怎样的?
    我们在调用目标函数之前,就会在本函数的栈顶上从右向左依次压入需要传递的参数,然后再创建好被调函数的栈帧后通过栈底寄存器的偏移量来访问形参,所以被调函数的形参不是在被调函数的栈帧空间中创建的,而是在调用函数的栈帧中创建的,且传参的顺序是从右至左。

  5. 函数的形参和实参的关系是什么?
    形参是实参的一份临时拷贝,二者虽处于同一个函数的栈帧空间内,但存储位置不同,形参的改变不会影响实参。

  6. 函数的返回值是如何带回的?
    函数的返回值通过eax寄存器带回。

  7. 函数是怎样在栈区上开辟和释放空间的?
    函数通过改变esp和edp的指向来创建和销毁空间 (即形成函数栈帧),空间销毁并不会清除该空间中的数据,下一次使用该空间时新数据直接覆盖原数据即可。


程序员内功心法之函数栈帧的创建和销毁相关推荐

  1. 程序员内功修炼——函数栈帧的创建与销毁

    一.什么是函数的栈帧 c语言是由函数构成的,那么函数是如何进行传参的?如何调用的?如何返回值的?这些问题与函数的栈帧有关. 函数栈帧:就是函数调用过程中程序的调用栈所开辟的空间,这些空间用来存放: 1 ...

  2. 【C语言】程序员筑基功法——《函数栈帧的创建与销毁》

    <函数栈帧的创建与销毁> 文章目录 1. 前言 2. 问题引入 3. 前提准备 3.1 寄存器 3.2 汇编指令 4. 函数栈帧的维护 5. 如何调用堆栈 6. 函数栈帧的创建和销毁 6. ...

  3. (动图详解)汇编视角观察函数栈帧的创建和销毁

    目录 ​1.阅读本文的价值 ​2.函数栈帧及栈的概念 ​3.部分寄存器及汇编指令 ​4.main函数的调用 5.main函数的栈帧创建 ​6.变量的栈帧创建 ​6.函数传参 ​7.函数内部运算及销毁 ...

  4. 函数栈帧的创建与销毁

    目录 前言 一.预备知识 1.内存区域的划分和分配 2.栈帧简介 3.寄存器简介 二.函数栈帧介绍 1.源代码 2.如何查看汇编代码 3.函数栈帧的创建与销毁(重点) 三.小彩蛋 总结 前言 最近在学 ...

  5. 函数调用过程详解:函数栈帧的创建与销毁

    前言:我们在学习C语言的过程中,可以会产生很多疑问,比如: 局部变量是怎么创建的 为什么局部变量的值不做初始化就是随机值 函数是怎么传参的?传参的顺序是怎么样的? 形参和实参是什么关系? 函数调用是怎 ...

  6. C语言内功修炼之函数栈帧的创建与销毁(举例加图解)

    大家可能会函数栈帧不了解,可能都没有听过这个,不用着急,在理解函数栈帧之前,我们先来了解一下程序对内存使用的分区大概情况:  区域 作用 栈区(stack) 由编译器自动分配和释放,存放函数的参数值, ...

  7. 内功修炼《函数栈帧的创建和销毁》建议收藏

    文章目录 前言 一. 寄存器的概念 二. 通用寄存器的结构 三. 指针寄存器和变址寄存器 四. EBP和ESP 五.总结 前言 在前期的学习过程中,我们可能会有很多的困惑: 1️⃣ 局部变量是怎么创建 ...

  8. 函数栈帧的创建和销毁图解

    目录 一.问题: 二.寄存器 栈区 1.寄存器有哪些?有什么作用? 2.编译环境 3.栈区的使用习惯: 4.main函数也是被其他函数调用的 5.汇编代码 三.为main函数创建栈帧 1.main函数 ...

  9. 一文带你深入了解函数栈帧的创建和销毁

    作者介绍:友友们好我是沐曦希,可以叫我小沐

最新文章

  1. value_counts()
  2. Vivado下几条 Verilog 综合规则
  3. 关于program patterns的一些思考
  4. NLP-基础知识-007(机器学习-朴素贝叶斯)
  5. Vue——Windows 10下Vue项目启动步骤[vue-cli-service启动版本]
  6. python协程处理海量文件_python_实战篇_使用协程gevent模块实现多任务copyA文件夹到B文件夹...
  7. Leetcode刷题(2)回文数
  8. node.js通过edge访问.net动态链接库
  9. 开启admin$共享
  10. 今天加班做了昨天晚上要写的页面,用到了一些之前用过但还不熟悉需要上网搜索才能用的知识点:...
  11. linux批量分区,Linux磁盘批量分区格式化和挂载脚本
  12. 经典算法题随机从连续的100个不重复数中取出100个不重复随机数
  13. LitePal简单用法
  14. IP地址后面/24/26/27/28/29/30网关数量分别是多少?如何计算?
  15. 帮Customer Architecture写的小脚本
  16. Windows和iPad传输
  17. ArcScene制作三维地图-三维模型
  18. 基于多策略改进的哈里斯鹰优化算法
  19. 括号画家——解题报告
  20. JS可折叠区域及accessbility实现(无障碍网页)

热门文章

  1. kkb接单平台项目与读书笔记
  2. 杭州各学历市内迁移落户信息
  3. php做宿舍门禁管理系统项目首选公司,一种校园宿舍门禁管理系统的制作方法
  4. js中将数组转换成字符串
  5. 报错org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException: sendDefaultImpl call time
  6. 数据竞赛专题 | 数据探索-从数据中发现隐藏价值
  7. 关于如何屏蔽迅雷极速版的升级提示
  8. matlab非线性电阻,用MATLAB处理电路中的非线性问题
  9. vue+element-ui 实现table中自带合计行数据改为所有数据的总计
  10. CAD编辑之创建表格、新建空白图纸以及创建和粘贴图块