这是我的“栈”争

一.什么是“栈”

“栈”的概念,是指它的访问规则。
“栈”的定义是,最后存入的东西,总是第一个被取走。
如下图,我们放入一个整形数据18
再放入一个整形数据20
注意,我们放的顺序是18 20 ,可当拿出数据时,先出来的却是20,后出来的是18。
就像是我们向木桶里放苹果,最后放进去的苹果是最先拿出的。
这是栈区别于其他结构的主要特点,即“后入先出”((last in first out)。

我先走啦!

二.“栈”在内存中的实现

在各种计算机系统中,最常见的栈实现方式如图所示。
它是由一段连续内存空间和一个寄存器(栈指针)组成。
栈指针是一个寄存器,它始终指向栈的顶部(即最近被压入的元素)。
每个被压入栈中的元素,在内存空间里占据一个独立的位置。

在这里插入图片描述
我们把放入数据成为压栈(一个苹果压在另一个苹果上面)。
首先,我们压入(PUSH)数据18,栈指针TOP移动,指向最后压入的值。
在这里插入图片描述
我们把弹出(POP)数据成为出栈(把一个苹果从另一个苹果上面拿走)。
上图中,我们压栈三次,出栈两次,TOP指针下移两次,指向最后压入的值。

三.函数栈帧的创建和销毁

1.我们先了解寄存器的概念
在这里插入图片描述
2.让我们以以下代码为例

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", c);
}

每一个函数调用,都要在栈区上创建一块空间。
正在调用哪个函数,ebp和esp就维护哪个函数的函数栈帧

进入main函数,ebp和esp维护main函数的函数栈帧
在这里插入图片描述
我们按下F10,右键转到反汇编,可看到以下代码
在这里插入图片描述
由于main函数是由__tmainCRTStartup调用的,
先分配__tmainCRTStartup的函数栈帧。
在这里插入图片描述
观察反汇编,第一行有push ebp
我们把ebp压入栈中,sep顺势上移指向ebp
在这里插入图片描述
我们看下一行

mov ebp,esp

mov是把后面的值给前面,
esp存的是地址,因此ebp上移到esp的位置上

sub esp,0e4h

esp地址减小了0e4h,向上移到低地址的某个位置
如下图所示:
在这里插入图片描述
而他们之间的空间就是为main函数开辟的
在这里插入图片描述
我们继续向下走:
在这里插入图片描述

遇到三个PUSH,在顶上压进三个元素。
在这里插入图片描述
继续向下:

lea edi ebp-04eh

lea即 Load effective address
诶嘿,好熟悉的04eh

mov ecx 39h
mov eax 0CCCCCCCCh
rep stos dword ptr es :[edi]

要把从刚刚edi开始向下的39h次double word 初始化为 0CCCCCCCCh
在这里插入图片描述
我们继续:
在这里插入图片描述
ebp-8处放置a
在这里插入图片描述
经过验证,b和a隔了两个字节
在这里插入图片描述

到这里,我们就明白了局部变量是如何创建的。

首先我们为函数调用创建函数栈帧,在函数栈帧里找到一些空间,把变量放进去。

继续向下:
在这里插入图片描述

mov eax,dword ptr [ebp-14h]
push eax

ebp-14h,这不是b吗?
把b的值放进eax里
压栈eax
同理得:
在这里插入图片描述
call指令调用函数,又把下一条指令的地址压栈

进入Add函数
在这里插入图片描述
进行同样的处理,可得:
在这里插入图片描述
在这里插入图片描述
把ebp+8的值给eax,这不是之前压栈的a吗
把ebp+12的值和ebp+8的值求和给eax,这不是a+b吗
最后把ebp-8的值(z)给寄存器eax
在这里插入图片描述
pop三次
在这里插入图片描述

mov esp,ebp

把ebp赋给esp,esp指向ebp

pop ebp

pop之后,ebp回到main函数的函数栈帧底部
esp向下移动一位,回到main函数的函数栈帧顶部
在这里插入图片描述

这块空间又由esp和ebp维护。

ret

ret返回call指令下一条指令的地址。

在这里插入图片描述

add esp,8

至此,esp+8,a和b的空间被销毁
并把z传出。

猜你喜欢

转载自blog.csdn.net/m0_63742310/article/details/123862343