本章目录
温馨提示
大家好我是Cbiltps,在我的博客中如果有难以理解的句意,难以用文字表达的重点,我会有配图。所以我的博客配图非常重要!!!
你想更好并顺利的学习函数栈帧章节,我强烈建议你看我写的【C语言初阶】调试技巧的博客,会有什么效果?等你看了并学习本章后就会感受到!!!
更重要的一点,在分析流程的时候是根据反汇编语言一步一步的进行的,但是这个过程用文字不好解释(非常繁多),于是我为了理解画了很长时间的图,所有的一切都在图中展示!!!切记,切记!如果还不理解,请联系我,我们可以打视频等方式……
如果你对我感兴趣请看我的第一篇博客!
开篇介绍
今天写的是【C语言进阶】
的第二篇内容:函数栈帧,函数栈帧真的是修炼内功!
以前很多地方是讲函数栈帧的,但是绝大多数同学是听不懂的,后来就少讲或者是不讲;而且发现公司不愿意考这些东西了(考的少)。
但是这个东西非常重要,如果你真的懂了这个东西,那真的是练成了神功!
学前疑惑
前期学习的时候,我们可能有很多困惑:
- 局部变量是如何创建的?
- 为什么局部变量的值是随机值?
- 函数是如何传参的?
- 函数传参的顺序是怎样的?
- 形参和实参是什么关系?
- 函数调用是如何做的?
- 函数调用结束后是如何返回的?
如果你想明白这些问题,等你学习完函数栈帧的创建和销毁后就知道了,其实就是修炼了自己的内功,也能搞懂后期更多的知识!
学前准备
1. 环境选择
在学习函数栈帧的时候,我使用的环境是 VS2013
(VC6.0
也是可以的,它对于函数栈帧的创建和销毁的过程是足够简单的),不要使用太高级的编译器,越高级的编译器,越不容易学习和观察(考虑各种问题,封装更加复杂)。
同时在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。
2. 知识铺垫
要想学习函数栈帧,就必须再给大家做一个小小的铺垫:了解寄存器(部分)!
数据寄存器:
- eax
- ebx
- ecx
- edx
指针寄存器:
- ebp
- esp
而本章节的重点是后指针寄存器(ebp
和esp
),要想理解函数栈帧,就必须理解这两个寄存器。
这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。
那ebp
和esp
是如何维护函数栈帧的呢?
正在调用哪个函数,esp
和ebp
维护的就是哪个函数的函数栈帧,esp
和ebp
之间的空间就是为这个函数开辟的空间。
正文开始
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
。
- 形参和实参是什么关系?
形参是实参的一分临时拷贝,值相同,空间独立。
改变形参,不会影响实参。
- 函数调用是如何做的?
理解上面的图!不好解释!
- 函数调用结束后是如何返回的?
理解上面的图!不好解释!