【C语言进阶】 ❤️ 顶级神功! 函数栈帧的创建和销毁!❤️

温馨提示

大家好我是Cbiltps,在我的博客中如果有难以理解的句意难以用文字表达的重点,我会有配图。所以我的博客配图非常重要!!!

你想更好顺利的学习函数栈帧章节,我强烈建议你看我写的【C语言初阶】调试技巧的博客,会有什么效果?等你看了并学习本章后就会感受到!!!

更重要的一点,在分析流程的时候是根据反汇编语言一步一步的进行的,但是这个过程用文字不好解释(非常繁多),于是我为了理解画了很长时间的图,所有的一切都在图中展示!!!切记,切记!如果还不理解,请联系我,我们可以打视频等方式……

如果你对我感兴趣请看我的第一篇博客

开篇介绍

今天写的是【C语言进阶】的第二篇内容:函数栈帧,函数栈帧真的是修炼内功!

以前很多地方是讲函数栈帧的,但是绝大多数同学是听不懂的,后来就少讲或者是不讲;而且发现公司不愿意考这些东西了(考的少)。

但是这个东西非常重要,如果你真的懂了这个东西,那真的是练成了神功!

学前疑惑

前期学习的时候,我们可能有很多困惑:

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

如果你想明白这些问题,等你学习完函数栈帧的创建和销毁后就知道了,其实就是修炼了自己的内功,也能搞懂后期更多的知识!

学前准备

1. 环境选择

在学习函数栈帧的时候,我使用的环境是 VS2013VC6.0也是可以的,它对于函数栈帧的创建和销毁的过程是足够简单的),不要使用太高级的编译器,越高级的编译器,越不容易学习和观察(考虑各种问题,封装更加复杂)。

同时在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。

2. 知识铺垫

要想学习函数栈帧,就必须再给大家做一个小小的铺垫:了解寄存器(部分)!

数据寄存器:

  • eax
  • ebx
  • ecx
  • edx

指针寄存器:

  • ebp
  • esp

而本章节的重点是后指针寄存器(ebpesp,要想理解函数栈帧,就必须理解这两个寄存器。

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

ebpesp是如何维护函数栈帧的呢?
正在调用哪个函数,espebp维护的就是哪个函数的函数栈帧,espebp之间的空间就是为这个函数开辟的空间。

正文开始


1. 大致轮廓了解(源代码及反汇编)


本章节会用下面一段代码举例:

#include <stdio.h>

int Add(int x, int y)
{
    
    
	int z = 0;
	z = x + y;
	return z;
}

int main()
{
    
    
	int a = 10;
	int b = 20;
	int c = 0;

	c = Add(a, b);

	printf("%d\n", c);

	return 0;
}

首先调用堆栈观察:其实这个调用逻辑还是挺复杂的!
在这里插入图片描述
然后画一个概念图理解(栈区):
在这里插入图片描述

上面的解析只是一个大概的轮廓,但是具体是怎么做的呢?

我们需要转到反汇编来探究:

int main()
{
    
    
000919F0  push        ebp  
000919F1  mov         ebp,esp  
000919F3  sub         esp,0E4h  
000919F9  push        ebx  
000919FA  push        esi  
000919FB  push        edi  
000919FC  lea         edi,[ebp-0E4h]  
00091A02  mov         ecx,39h  
00091A07  mov         eax,0CCCCCCCCh  
00091A0C  rep stos    dword ptr es:[edi]  
	int a = 10;
00091A0E  mov         dword ptr [a],0Ah  
	int b = 20;
00091A15  mov         dword ptr [b],14h  
	int c = 0;
00091A1C  mov         dword ptr [c],0  

	c = Add(a, b);
00091A23  mov         eax,dword ptr [b]  
00091A26  push        eax  
00091A27  mov         ecx,dword ptr [a]  
00091A2A  push        ecx  
00091A2B  call        _Add (0911DBh)  
00091A30  add         esp,8  
00091A33  mov         dword ptr [c],eax  

	printf("%d\n", c);
00091A36  mov         esi,esp  

	printf("%d\n", c);
00091A38  mov         eax,dword ptr [c]  
00091A3B  push        eax  
00091A3C  push        95858h  
00091A41  call        dword ptr ds:[99114h]  
00091A47  add         esp,8  
00091A4A  cmp         esi,esp  
00091A4C  call        __RTC_CheckEsp (091136h)  

	return 0;
00091A51  xor         eax,eax  
}
00091A53  pop         edi  
00091A54  pop         esi  
00091A55  pop         ebx  
00091A56  add         esp,0E4h  
00091A5C  cmp         ebp,esp  
00091A5E  call        __RTC_CheckEsp (091136h)  
00091A63  mov         esp,ebp  
00091A65  pop         ebp  
00091A66  ret  

上面的代码就是反汇编代码,下面我们就开始学习吧!


2. 函数栈帧的创建和销毁总流程


在这里插入图片描述


3.函数栈帧的创建


3.1 main函数的创建(图片展示)

在这里插入图片描述

3.2 Add函数的创建(分解)

在这里插入图片描述


4.函数栈帧的销毁


4.1 main函数的销毁(图片展示)

在这里插入图片描述

5.1 Add函数的销毁(部分反汇编代码展示)

前面的部分和main函数的一样,所以不做过多的介绍!
在这里插入图片描述


5. 问题回答


  • 局部变量是如何创建的?

首先为函数分配栈帧空间,在栈帧空间初始化一部分的空间后,在栈帧中给局部变量分配空间。

  • 为什么局部变量的值是随机值?

在这里插入图片描述
在栈帧空间初始化一部分的空间,其实就是赋了很多的'C',这就是随机值。
而以前打印出来看到的:烫烫烫烫烫烫烫烫烫烫烫烫…… 就是因为局部变量的值是随机值。

  • 函数是如何传参的?

直接看图:
在这里插入图片描述

  • 函数传参的顺序是怎样的?

其实还没有调用函数的时候,已经从右向左 push

  • 形参和实参是什么关系?

形参是实参的一分临时拷贝,值相同,空间独立。
改变形参,不会影响实参。

  • 函数调用是如何做的?

理解上面的图!不好解释!

  • 函数调用结束后是如何返回的?

理解上面的图!不好解释!

全文结束

Supongo que te gusta

Origin blog.csdn.net/Cbiltps/article/details/120581909
Recomendado
Clasificación