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


    在我们之前学习函数的时候,我们可能有很多困惑?

    比如:

    • 局部变量是怎么创建的?
    • 为什么局部变量的值是随机值?
    • 函数是怎么传参的?传参的顺序是怎样的?
    • 形参和实参是什么关系?
    • 函数调用是怎么做的?
    • 函数调用是结束后怎么返回的?

    那么要解决这些问题, 我们就需要知道函数栈帧的创建和销毁.

    本文的环境为VS2013, 注意函数栈帧创建和销毁的过程在不同编译器下是有略微差异的.
    那么要想了解函数的栈帧, 就需要再了解一些前置知识.

    1. 寄存器与函数栈帧的概念

    寄存器是计算机体系结构中的一种关键元素,用于存储和处理数据。

    常见的寄存器有eax, ebx, ecx, edx...这些寄存器在后文都会遇到. 我们要重点介绍两个寄存器, 一个是ebp, 一个是esp.

    要想理解函数栈帧, 就必须了解ebp和esp这两个寄存器.

    ebp, esp这两个寄存器中存放的是地址, 这两个地址是用来维护函数栈帧的.

    那么这两个寄存器是怎么来维护函数栈帧的呢?

    之前我们有提到, 每一个函数调用都要在栈区创建一个空间.

    我们使用VS2013写下以下代码:

    1. #include <stdio.h>
    2. int Add(int x, int y) {
    3. int z = 0;
    4. z = x + y;
    5. return z;
    6. }
    7. int main() {
    8. int a = 10;
    9. int b = 10;
    10. int c = 0;
    11. c = Add(a, b);
    12. printf("%d\n", c);
    13. return 0;
    14. }

    我们知道,对于这样一个代码, 每一个函数的调用都要为它开辟一块内存空间.

    2. Add程序的底层调用细节分析

    接下来我们通过调试来展现上文的Add程序是如何调用的.

    在VS2013编译器按下F10,此时不要动,右击鼠标转到反汇编.

    此时,就能看到C语言所对应的反汇编代码.

    我们来一步步调试并分析.

    首先要知道的是main函数是被别人调用的, 在这个地方马上要进入main函数调用的时候另外调用main函数的函数栈帧已经创建好了.

    接下来我们进入main函数,第一步为push(压栈)

    注意此时esp地址应该是变小了, 如下图, F10往下走一步可以看到, esp地址变小.


    接下来继续看.

    ebp 压栈之后 mov ebp,esp 也就是把esp的值给ebp,即

    接下来 sub减法 给 esp 减去 0E4h(16进制数字).

    这意味着esp本来存的ebp的地址, 减0E4h之后地址变小, 指向上面某一块区域


    接下来继续看.

    接下来是3个push ebx esi edi. 与前面ebp类似

    往下走.

    往下.

    走到这里,main函数的栈帧就开辟完成了.


    接下来我们就要执行正式有效的代码, 也就是我们所编写的源代码.

    这里是创建变量的时候给它赋值,int a = 10;
    如果创建变量的时候没有给它赋值,那么这个地方就不会把10放进去.
    那么内存中默认放的就是CCCCCCCC.
    之前我们在内存中打印出了随机值”烫烫烫烫”,就出自于此.

    这就是变量为什么要初始化如果不初始化,这里就放的是随机值.


    思考: 当函数调用往下走的时候, 函数调用需要传参,它是如何传参的?


    我们再接着往下.
    后面可以看到Add函数是如何被创建与调用的, 又是如何进入Add函数, 回到main函数, 等各种问题.

    此时要按F11 跳到这里,可以看到内存中多了一个00a61450,刚好就是上面call的下一条指令

    那么就是 执行call指令的时候,把call指令的下一条指令的地址压栈

    再按一次F11,就进到函数中了, 这次是真正来到Add函数中

    接下来我们分析Add函数, 其实和main差不多.


    接着往下看Add函数的执行.

    接着往下


    我们接着往下.


    到这里,z还没有返回,那么我们往下走看一下是怎么返回结果z的





    至此, Add程序的整个调用细节到此结束.
  • 相关阅读:
    鸿蒙Harmony应用开发—ArkTS声明式开发(模态转场设置:半模态转场)
    Rxjava3 全新详解及常用操作符
    三极管原理介绍
    kotlin retrofit
    VMware认证考试科目及课程内容
    “安全生产月”专题报道:AI智能监控技术如何助力安全生产
    sublime 文件编辑器使用快捷键
    阿里云通义千问大模型正式开放;玩10次ChatGPT就要消耗1升水
    Java基础 —— 项目一:ATM存取款系统
    Gateway网关动态路由配置(YAML格式 + JSON格式)
  • 原文地址:https://blog.csdn.net/YeZh1Yong_Cr/article/details/133549549