函数栈帧的创建和销毁介绍

简介: 函数栈帧的创建和销毁介绍




首先理解一下寄存器:

eax,ebx,ecx,edx,ebp,esp。画横线的这两个寄存器存放的是地址。这两个地址是用来维护函数栈帧的。

每一次函数调用,都要在栈区创立一个空间。

什么是栈?

函数通过栈来实现控制转移、参数传递、局部变量的分配和释放3个功能。 计算机有专门的一块内存区域作为栈,每个函数都可以在栈上申请一块内存区域作为函数的存储空间,而该存储空间则被称为函数的栈帧。

栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push),也可 以将已经压入栈中的数据弹出(出栈,pop),但是栈这个容器必须遵守一条规则:先入栈的数据后出栈

编写代码

详细解释栈帧创立和销毁过程

如下图所示,在栈区(计算机专门的内存空间),每个函数在栈区申请一块内存空间,称为函数栈帧。在调用哪个函数,esp和ebp就跑去维护哪个函数的栈帧。我们通常把ebp称为栈底指针,esp称为栈顶指针。栈区的使用情况是先使用高地址,再使用低地址(向下增长)。

点调试窗口。按下图进行操作。

我们在函数执行完的时候,可以在调用堆栈中看到:

main函数被__tmainCRTStartuo()调用。

而上面的函数又被上图中下面一个函数调用。

esp和ebp首先维护main函数,再调用add函数。

此时ebp,esp维护的是这样一个空间。进入main函数的第一步是push。push压栈,栈里面放的是ebp。当push完成之后,esp指到栈顶。

我们也可以在监视中观察到:1

2

我们可以观察到esp的地址 减少了4,意味着esp往上了4位。

接着执行move,move是把esp的值给ebp。那么ebp就不再指向原来的地方了。

同样的,也可以在监视窗口中看到

接着执行sub,给esp减去0E4h(16进制数字)。意味着esp指向上面的某一块位置。

这块空间就是为main函数申请的空间。紧接着,push三次。顶上压了个元素ebx,push完之后esp向上指。执行完成之后,在顶上压了三个元素。

lea:load effective address   加载有效地址

接着,lea,ebp-0E4h

接着执行到rep stos,把39h里的双字(4个字节)全部改为eax的内容0CCCCCCCCh 。

到ebp结束。往上所有的空间都初始化为cccccccc。

为啥不初始化打印出来的就是烫烫烫烫呢?

是因为main函数调用时,在栈区开辟的空间的其中每一 个字节都被初始化为0xCC,而arr数组是一个未初始化的数组,恰好在这块空间上创建的,0xCCCC(两 个连续排列的0xCC)的汉字编码就是“烫”,所以0xCCCC被当作文本就是“烫”。

接着执行的语句让ebp-8中放入了一个10。(与上一个数值之间差了两个整型)

接着调用add函数,

接着在栈顶压20。

mov 把ebp-14h放到eax里。也就是把20的值放到eax里面去了,然后push eax,eax压栈,里面存放的是20。然后move,把 ebp-8的值放到了ecx当中。push,顶上又压了一个ecx10。

接下来call指令调用函数,按F11。call指令又把add函数的地址压到顶上来了。(call指令的下一条指令的地址)

然后来到了add函数

前面这一堆和原先的函数内容一样。

sub,给esp减去一个0CCh

然后又是三次push。edi到ebp之间的所有空间全部初始化为CCCCCCh。

ebp+8

ebp+8把a撇b撇找过来了。把ebp+8的值加到 eax里,再把ebp+12的值加到eax里。再把算出的结果30放到ebp-8里面去。我们可以发现参数是从右向左传的。形参不是在add函数内部创建的,而是找到刚刚传参压过去的空间。a和b就会分别被认为是x和y。在没有调用add函数时,参数就已经传过去。我们可以说,形参是实参的一份临时拷贝。改变形参a撇b撇,不改变实参。

最后一步return z,z是怎么返回的呢?把ebp-8的值放到eax里面去。eax是个寄存器,寄存器是不会退出就销毁的。ebp-8就是z,里面存放着30的值。等回到主函数时,再把eax的值拿出来用就行了。

三次pop弹出 再把ebp赋给esp。然后再pop一下,把栈顶的元素弹出来,栈顶弹的是main函数的ebp。ebp的地址存在main函数当中,就是要让随着函数调用返回之后,随着栈帧的销毁,栈顶是很容易找到的,但是栈底不容易找到。pop弹出,ebp走了。

ebp就回回去了。pop一下找到了main函数的栈帧空间。

这样就顺顺利利地回到了main函数里头了,还应该从call指令的下一条指令执行。顶上放着call指令下一条指令的地址,在栈顶存这个地址就是为了在函数调用完之后还能回来,回来之后还能从call指令的下一条指令往下执行。在函数调用完成之后,形参x和y就没有作用了

ebp+8。esp指向移动8个字节,上面的空间不属于。再把eax的值放到ebp-20h当中。eax的值就是出add函数时委托到eax当中的和,和放到局部变量c当中,这样返回值就带回来了。

解决疑惑

局部变量是如何创建的?

首先为函数分配好栈帧空间,栈帧空间初始化好一部分空间之后,然后给局部变量在栈帧里分配一点空间。

为什么局部变量不初始化内容是随机的?

随机值是被随机放入的。如果初始化,就相当于把随机值覆盖了。

函数调用时参数时如何传递的?

当没有调用函数的时候已经pushpush把两个参数从右向左开始压栈压进去了,当真的进入形参函数的时候,其实在add函数栈帧里,通过指针的偏移量找回了形参。

函数的返回值是如何带会的?

调用之前就把call指令的下一条指令的地址记住了,当往回返的时候,就可以跳转到call指令下一条指令的地址,返回值是通过寄存器的方式调用回来的。

欢迎交流!

相关文章
|
6月前
|
编译器
函数栈帧的创建和销毁
函数栈帧的创建和销毁
32 0
|
7月前
|
存储 编译器 容器
函数栈帧的创建和销毁讲解
函数栈帧的创建和销毁讲解
48 0
|
7月前
|
编译器 容器
关于函数栈帧的创建和销毁
关于函数栈帧的创建和销毁
|
存储
函数栈帧的创建和销毁(下)
函数栈帧的创建和销毁(下)
59 0
|
7月前
|
存储 编译器
初识函数栈帧的创建与销毁(笔记)
初识函数栈帧的创建与销毁(笔记)
|
编译器 程序员 C语言
函数栈帧的创建与销毁(超详解)
函数栈帧的创建与销毁(超详解)
118 0
|
存储 缓存 编译器
函数栈帧的创建与销毁
函数栈帧的创建与销毁
47 0
|
存储 C语言 C++
你知道函数栈帧的创建和销毁吗?
你知道函数栈帧的创建和销毁吗?
78 0
|
存储 编译器 C++
深入理解内存 —— 函数栈帧的创建与销毁
深入理解内存 —— 函数栈帧的创建与销毁
139 0
|
编译器 C语言 容器
函数栈帧的创建和销毁(一)
函数栈帧的创建和销毁
120 1