噬人之风|这是我的“栈”争

一.什么是“栈”

“栈”的概念,是指它的访问规则。
“栈”的定义是,最后存入的东西,总是第一个被取走。
如下图,我们放入一个整形数据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传出。

    推荐阅读