简单来说栈的主要特点有:
一个限定表尾进行删除(出栈)和插入(入栈)操作的线性表,其过程类似与压子弹与退子弹(后进先出)。 一个由系统自动分配的内存空间,譬如调用函数、创建临时变量时内存空间的创建与销毁。 用于存储函数内部的局部变量、方法调用、函数传参数值等。 由高地址向低地址生长。
寄存器 | 用途 |
---|---|
EAX | 累加寄存器:用于乘除法、函数返回值 |
EBX | 用于存放内存数据指针 |
ECX | 计数器 |
EDX | 用于乘除法、IO指针 |
ESI | 源索引寄存器,存放源字符串指针 |
EDI | 目标索引寄存器,存放目标字符串指针 |
ESP | 存放栈顶指针 |
EBP | 存放栈底指针 |
汇编指令 | 用途 |
---|---|
mov | mov A,B 将数据B移动到A |
push | 压栈 |
pop | 出栈 |
call | 函数调用 |
add | 加法 |
sub | 减法 |
rep | 重复 |
lea | 加载有效地址 |
首先,什么是栈帧?引用百度百科:C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。从这句话中,可以提炼以下几点信息:
下面进入主题,图解函数栈帧的创建与销毁过程。
根据VS2013编译器调试,调用堆栈,不难发现main函数的调用链条如下:
很显然main函数在被调用时,创建了栈帧。在调试过程中将转到反汇编,便能直观的看到main函数栈帧创建的过程。首先需明确的是,函数栈帧由寄存器esp,ebp维护。
PLAINTEXT
008B1410 push ebp
008B1411 mov ebp,esp
008B1413 sub esp,0E4h
008B1419 push ebx
008B141A push esi
008B141B push edi
008B141C lea edi,[ebp-0E4h]
008B1422 mov ecx,39h
008B1427 mov eax,0CCCCCCCCh
008B142C rep stos dword ptr es:[edi] //dword 为 4个字节
1.在__tmainCRTStartup()函数顶部压入ebp,如图所示esp指向ebp,ebp成功压入栈中。
2.esp值传递给ebp。
3.esp减去0E4h:由于栈先使用高地址后使用低地址,减去一个值意味着esp指针向低地址移动了0E4h个地址,此处便开辟了main函数的栈帧。
4.压入ebx,esp指向ebx顶部。
5.压入esi,esp指向esi顶部。
6.压入edi,esp指向edi顶部。
7.将edi向下39h个空间全部改为0xCCCCCCCC。
PLAINTEXT
int a = 10;
00AA142E mov dword ptr [ebp-8],0Ah
int b = 20;
00AA1435 mov dword ptr [ebp-14h],14h
int ret = 0;
00AA143C mov dword ptr [ebp-20h],0
CPP
ret = Add(a, b);
00AA1443 mov eax,dword ptr [ebp-14h]
00AA1446 push eax
00AA1447 mov ecx,dword ptr [ebp-8]
00AA144A push ecx
00AA144B call 00AA10E1
00AA1450 add esp,8
00AA1453 mov dword ptr [ebp-20h],eax
可以发现,在执行call指令后,栈中压入call指令的下一条地址。
进入Add()函数,可以看出这与此前main函数开辟栈帧的过程类似,说明Add()函数调用又开辟了一块独立的栈帧。
在函数栈帧、局部变量创建完毕后,进行Add()函数运算过程:
PLAINTEXT
c = a + b;
00AA13E5 mov eax,dword ptr [ebp+8]
00AA13E8 add eax,dword ptr [ebp+0Ch]
00AA13EB mov dword ptr [ebp-8],eax
通过上述过程可以得知函数内部并未给形参开辟空间,而是直接查找了实参传递时的地址,由此解释了形参其实是实参的一份临时拷贝。
PLAINTEXT
return c;
00AA13EE mov eax,dword ptr [ebp-8]
将返回值传递至寄存器eax中,因此在函数调用结束函数栈帧被销毁时,返回值并不会销毁。在函数拿到返回值后,开始出栈:
PLAINTEXT
00AA13F1 pop edi
00AA13F2 pop esi
00AA13F3 pop ebx
00AA13F4 mov esp,ebp
00AA13F6 pop ebp
00AA13F7 ret
从低位置到高位置依次弹出edi,esi,ebx,随后将ebp赋给esp并弹出ebp,最后执行ret指令返回到调用Add函数的call指令的下一地址,在执行ret指令时实际已弹出After call,以执行指令 add esp,8,此时esp向高地址移动8字节,esp,ebp重新维护main函数,eax中存放的返回值将被传递给地址(ebp - 20h)即ret的地址。至此,Add函数返回完毕。main函数栈帧销毁过程与前述过程类似。
文章作者: CtrlX
文章链接: http://ctrlx.life/post/8343ef68.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 CtrlCherry!
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有