一.什么是“栈”
“栈”的概念,是指它的访问规则。二.“栈”在内存中的实现
“栈”的定义是,最后存入的东西,总是第一个被取走。
如下图,我们放入一个整形数据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);
}
每一个函数调用,都要在栈区上创建一块空间。进入main函数,ebp和esp维护main函数的函数栈帧
正在调用哪个函数,ebp和esp就维护哪个函数的函数栈帧
文章图片
我们按下F10,右键转到反汇编,可看到以下代码
文章图片
由于main函数是由__tmainCRTStartup调用的,
先分配__tmainCRTStartup的函数栈帧。
文章图片
观察反汇编,第一行有push ebp
我们把ebp压入栈中,sep顺势上移指向ebp
文章图片
我们看下一行
mov ebp,espmov是把后面的值给前面,
esp存的是地址,因此ebp上移到esp的位置上
sub esp,0e4hesp地址减小了0e4h,向上移到低地址的某个位置
如下图所示:
文章图片
而他们之间的空间就是为main函数开辟的
文章图片
我们继续向下走:
文章图片
遇到三个PUSH,在顶上压进三个元素。
文章图片
继续向下:
lea edi ebp-04ehlea即 Load effective address
诶嘿,好熟悉的04eh
mov ecx 39h要把从刚刚edi开始向下的39h次double word 初始化为 0CCCCCCCCh
mov eax 0CCCCCCCCh
rep stos dword ptr es :[edi]
文章图片
我们继续:
文章图片
ebp-8处放置a
文章图片
经过验证,b和a隔了两个字节
文章图片
到这里,我们就明白了局部变量是如何创建的。
【噬人之风|这是我的“栈”争】首先我们为函数调用创建函数栈帧,在函数栈帧里找到一些空间,把变量放进去。继续向下:
文章图片
mov eax,dword ptr [ebp-14h]ebp-14h,这不是b吗?
push eax
把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 ebppop之后,ebp回到main函数的函数栈帧底部
esp向下移动一位,回到main函数的函数栈帧顶部
文章图片
这块空间又由esp和ebp维护。
retret返回call指令下一条指令的地址。
文章图片
add esp,8至此,esp+8,a和b的空间被销毁
并把z传出。
推荐阅读
- c语言|leetcode707 设计链表 (C语言实现)
- C语言标准库|C语言保留字
- C进阶|C语言深度解析之六(自定义类型详解(结构体+枚举+联合))
- C语言重难点进阶|自定义类型详解(结构体+枚举+联合)【C进阶】
- C语言|C语言--自定义类型详解(结构体+枚举+联合)
- C语言初阶|自定义类型详解(结构体+枚举+联合)
- html|自定义类型~结构体~位段~枚举~联合~超详解~一遍就会
- 小程序|C语言实现通讯录
- C语言笔记|c语言入门笔记