深度解剖之函数栈帧的创建和销毁原理(上)
博主小此方在CSDN博客中详细解析了函数调用过程中栈帧的创建机制。文章以VS2013编译器为例,通过反汇编代码展示了main函数栈帧的开辟过程,包括压栈、寄存器操作和内存初始化等步骤。重点解释了局部变量未初始化时出现"烫烫烫"乱码的原因(内存被初始化为0xCC),并介绍了esp、ebp等关键寄存器在维护栈帧中的作用。文章还简要提及了main函数并非程序真正起点的事实。这篇技术文

◆ 博主名称: 小此方-CSDN博客
大家好,欢迎来到小此方的博客。
🔥个人专栏:《C语言》_小此方的博客-CSDN博客
🔥 努力成就未来,代码改变世界,相信我有一天也能成为改变世界的那个人

●局部变量是怎么创建的?
●为什么局部变量的值是随机值?
●函数是怎么传参的?传参的顺序是怎样的?
●形参和实参是什么关系?
●函数调用是怎么做的?
●函数调用结束后怎么返回的?
你是否还在为这些问题而困扰?
没关系,看完这篇文章,你一定能对这些问题门儿清。
让我们直接上正片:
(以以下代码为例,考虑到编译器版本过新,对栈帧创建和销毁的封装就越深,越不容易被观察;所以本次采用VS2013编译器进行观察。)
int Add(int a,int b)
{
z=a+b;
return z;
}
int main ()
{
int a=10;
int b=20;
int c=Add(a,b);
return 0;
}
F10开始调试:右击代码转到反汇编:如图

一,反汇编界面介绍
如图,当我们打开反汇编界面:
◆ 淡蓝色部分:该项汇编语句执行的操作。
◆ 橙色部分:执行的操作内容。
◆ 深红色部分:该语句的地址。
◆ 深蓝色部分:源代码。
建议将右上角的显示符号名关闭;方便观察。
二,怎样为main函数开辟栈帧
1.函数的起点
如果在反汇编页面翻一翻,你会看到,事实上main并不是整个”程序真正的出发点“

● mainCRTStartup()调用了__tmainCRTStartup();
● __tmainCRTStartup()再调用了main()函数。所以我们画一个图:

2.寄存器
接下来为大家介绍一个新概念:寄存器
寄存器是CPU内部的高速存储单元,用于临时存放指令、数据和地址。其读写速度远高于内存,用于支持CPU的快速运算和数据交换。
常见的寄存器有这些:eax,ebx,ecx,edx.
以及两个特殊寄存器:esp和ebp(用来维护被创建出来的函数栈帧)
如图:

esp被称为栈顶指针;ebp被称为栈底指针。
分别指向这块函数栈帧的顶部和底部:
3.压栈,开辟,填充
回到反汇编这张图

在main任然没有被调用时,
esp和ebp被用来维护 __tmainCRTStartup()函数的栈帧,。
我们可以将函数栈帧的开辟想想成搭积木(房子)
◆ 第一步:”push“,这一步被称为压栈:顾名思义就是将数据压到当前栈帧的顶部。
压栈分为两个小步骤:压数据和移动栈顶指针:相当于把房子造高了,房顶(esp)也就跟着变高了。
◆ 第二步:“mov”(move)移动:就是将esp的数据(栈顶地址)给到ebp,让ebp指向esp所指向的位置(如图)。

◆ 第三步:“sub”做减法:意思就是esp指针减去0E4h这个十六进制数字。我们把房顶抬升到了一个非常高的高度:main函数的栈帧初步创建完成。
三,烫烫烫烫烫烫烫烫
”手持两把锟斤拷,口中疾呼烫烫烫“
这是在时是每个程序员都知道的一句梗:也就是”乱码“。当我们试图打印没有被初始化的变量时也会出现这种情况。原因在这里
接着前文,我们继续分析:
◆ 第四步:我们将ebp-0E4h给到edi(如图),
◆ 接下来三步是合在一起的
39h这个数字存入ecx寄存器,0CCCCCCCCh存入eax寄存器
【插曲:word表示双字两个字节,dword(double word)表示两倍双字:也就是4个字节】
dwoed ptr es : [edi]表示:将从edi开始的39h个长度的内存全部初始化为0CCCCCCCCh。
◆ 第八步:

为变量开辟空间:如图:我们为变量a,b,c分别在ebp-8h,ebp-14h,ebp-20h处创建空间,并赋值。赋值是关键!如果不赋值:当我们打印比如a的时候,我们实际上访问到的0CCCCCCCCh。
解析出来也就是乱码:烫烫烫烫烫烫烫烫。
本期内容就到这里,感谢大家的观看!
更多推荐



所有评论(0)