【C语言】函数栈帧的相关知识,希望可以帮助你

引言 |  函数栈帧实质是指在内存栈区为函数所开辟的一块空间,使函数得以运行,本章简要介绍函数栈帧的创建及销毁,涉及少量的汇编代码。        VS2013演示

1.寄存器

 寄存器是集成在中央处理器上的元件,可以临时存储数据(就像内存一样,但其访问速度较内存快),例如eax  ebx  ecx  edx  ebp  esp  ,其中ebp(栈底指针),esp(栈顶指针)存放的是地址,这两的存放的地址用于维护函数指针;

ebp(栈底指针)  esp(栈顶指针)

压栈(push):向栈顶放置一个元素; 出栈(pop):从栈空间拿走一个元素

2.main()主函数

在实际程序运行中,main()函数也是被其他的函数调用,在写好实例代码后:

#include<stdio.h>

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main()
{
	int a = 13;
	int b = 12;
	int c = 0;

	c = Add(a, b);

	printf("%d\n", c);

	return 0;
}

 按下F10启动逐过程调试,调试->窗口->调用堆栈,

(这里看到main()函数被调用)调试至结束后(return 0;语句走完);

由于栈空间是从高地址向低地址的次序使用的,故mainCRTStartup()函数首先调用__tmainCRTStartup()函数,之后__tmainCRTStartup()函数调用main()函数,可以在源代码ctrexe.c中找到相关函数名及调用方式;

 3.main()函数及Add()函数运行是对应的汇编代码;

在shiift+F5退出后,再按F10进入调试,调试->窗口->反汇编,找到对应的汇编代码;

3.1. main()主函数的对应汇编代码

--- d:\learningfile\class103\test\函数栈帧\函数栈帧\test.c ----
int main()
{
01011410  push        ebp  
01011411  mov         ebp,esp  
01011413  sub         esp,0E4h  
01011419  push        ebx  
0101141A  push        esi  
0101141B  push        edi  
0101141C  lea         edi,[ebp-0E4h]  
01011422  mov         ecx,39h  
01011427  mov         eax,0CCCCCCCCh  
0101142C  rep stos    dword ptr es:[edi]  
	int a = 13;
0101142E  mov         dword ptr [ebp-8],0Dh  
	int b = 12;
01011435  mov         dword ptr [ebp-14h],0Ch  
	int c = 0;
0101143C  mov         dword ptr [ebp-20h],0  

	c = Add(a, b);
01011443  mov         eax,dword ptr [ebp-14h]  
01011446  push        eax  
01011447  mov         ecx,dword ptr [ebp-8]  
0101144A  push        ecx  
0101144B  call        010110E1  
01011450  add         esp,8  
01011453  mov         dword ptr [ebp-20h],eax  

	printf("%d\n", c);
01011456  mov         esi,esp  
01011458  mov         eax,dword ptr [ebp-20h]  
0101145B  push        eax  
0101145C  push        1015858h  
01011461  call        dword ptr ds:[01019114h]  
01011467  add         esp,8  
0101146A  cmp         esi,esp  
0101146C  call        0101113B  

	return 0;
01011471  xor         eax,eax  
}
01011473  pop         edi  
}
01011474  pop         esi  
01011475  pop         ebx  
01011476  add         esp,0E4h  
0101147C  cmp         ebp,esp  
0101147E  call        0101113B  
01011483  mov         esp,ebp  
01011485  pop         ebp  
01011486  ret  

 3.2. Add()函数对应的汇编代码:

--- d:\learningfile\class103\test\函数栈帧\函数栈帧\test.c -------
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int Add(int x, int y)
{
010113C0  push        ebp  
010113C1  mov         ebp,esp  
010113C3  sub         esp,0CCh  
010113C9  push        ebx  
010113CA  push        esi  
010113CB  push        edi  
010113CC  lea         edi,[ebpebp-0CCh]  
010113D2  mov         ecx,33h  
010113D7  mov         eax,0CCCCCCCCh  
010113DC  rep stos    dword ptr es:[edi]  
	int z = 0;
010113DE  mov         dword ptr [ebp-8],0  
	z = x + y;
010113E5  mov         eax,dword ptr [ebp+8]  
010113E8  add         eax,dword ptr [ebp+0Ch]  
010113EB  mov         dword ptr [ebp-8],eax  
	return z;
010113EE  mov         eax,dword ptr [ebp-8]  
}
010113F1  pop         edi  
}
010113F2  pop         esi  
010113F3  pop         ebx  
010113F4  mov         esp,ebp  
010113F6  pop         ebp  
010113F7  ret  

main()汇编代码的理解:在执行main函数前,__tmainCRTStartup()函数的栈帧已存在,其中epb指向该函数栈帧的底部(高地址处),esp指向该函数栈帧的顶部;

 

 main()函数栈帧预开辟:

01011410  push        ebp  
01011411  mov         ebp,esp  
01011413  sub         esp,0E4h 

push指令(压栈)表示将ebp的值压入栈中,产生新的栈顶,同时esp存放地址会减小,保持其存放地址始终是栈顶,mov指令表示将esp的值赋值给ebp,时ebp指向的地址发生改变,sub指令表示减法,将esp减去0E4h(h表示该数字为十六进制)后更新esp,此时ebp与esp之间的空间即为main()函数的函数栈帧,如图:

 

 

01011419  push        ebx  
0101141A  push        esi  
0101141B  push        edi 

三个push指令表示将这三个值压入栈中,同时esp会发生相应减小:

 

 

0101141C  lea         edi,[ebp-0E4h]  
01011422  mov         ecx,39h  
01011427  mov         eax,0CCCCCCCCh  
0101142C  rep stos    dword ptr es:[edi]

lea指令(load effective address加载有效地址)表示将ebp中存放的地址值减去0E4h后存放在edi中;mov指令表示分别将39h与0cccccccch放入ecx与eax中;rep stos指令表示将以edi存放的地址开始向下的ecx次个dword的字节(word表示两个字节,dword即double word,表示4字节)修改为eax的内容,由于0E4h = ecx * dword ,故main的函数栈帧被全部更改为cc;

	int a = 13;
0101142E  mov         dword ptr [ebp-8],0Dh 

mov指令表示将0Dh的值(对应的十进制是13)放入epb-8字节的地址处,此时int占据4个字节,使用完后离ebp所指向的真实地址还有4个空间,这样设计是为了防止越界访问造成的影响:

 

 

这就是为什么在局部变量未赋值时为“烫烫”的原因,里面全部都是cc;接下来的赋值指令都是如此执行的,变量之间相差了8个字节 ;

	c = Add(a, b);
01011443  mov         eax,dword ptr [ebp-14h]  
01011446  push        eax  
01011447  mov         ecx,dword ptr [ebp-8]  
0101144A  push        ecx  
0101144B  call        010110E1  
01011450  add         esp,8  
01011453  mov         dword ptr [ebp-20h],eax 

mov指令表示将ebp-14h处4字节的空间中的值放入eax寄存器中,push指令表示将eax压入栈区,(这里即是创建形参,注意是从右向左传参,先将最右的值压栈,逐渐向右压栈)esp存放地址改变;在执行call指令前,按下F11(逐语句调试)会出现:

_Add:
00FC10E1  jmp         00FC13C0 

call指令表示执行之后的地址010110E1处的指令,以及将call指令下一条指令的地址(01011450)压入栈中,jmp指令表示跳至00FC13C0地址处,即开始执行Add函数汇编代码,经过预开辟空间,赋值:

int Add(int x, int y)
{
010113C0  push        ebp  
010113C1  mov         ebp,esp  
010113C3  sub         esp,0CCh  
010113C9  push        ebx  
010113CA  push        esi  
010113CB  push        edi  

 对应图示:

 

 

 

下一部分: 

	int z = 0;
010113DE  mov         dword ptr [ebp-8],0  
	z = x + y;
010113E5  mov         eax,dword ptr [ebp+8]  
010113E8  add         eax,dword ptr [ebp+0Ch]  
010113EB  mov         dword ptr [ebp-8],eax  
	return z;
010113EE  mov         eax,dword ptr [ebp-8]  
}

 010113E5代码与010113E8代码通过ebp加整数方式找到ecx将里面的值赋给eax,在通过此方法将eax的值找到加给eax(更新eax的值),010113EB代码将eax的值放入[epb - 8]的四个字节的空间(即z的空间)中,010113EE代码将[epb - 8]的四个字节的空间的值重新放入eax寄存器,防止Add函数空间释放后无法找到所需要的值

	return z;
00FC13EE  mov         eax,dword ptr [ebp-8]  
}
00FC13F1  pop         edi  
00FC13F2  pop         esi  
00FC13F3  pop         ebx  
00FC13F4  mov         esp,ebp  
00FC13F6  pop         ebp  
00FC13F7  ret 

 三次将寄存器弹出后,esp被mov指令指向ebp所指向空间,在pop指令将epb弹出后,esp指向更新,同时ebp重新指向main()函数栈底;ret是执行流程导向 01011450 指令,此时执行流程回到主函数(读完01011450 地址后,esp自动更新):

 

 

01011450  add         esp,8  
01011453  mov         dword ptr [ebp-20h],eax  

add指令 表示esp重新指向esp+8地址,ecx与eax两个空间;mov指令表示将exa里的值放入[ebp-20h]所指向的4字节空间,即变量c的空间,函数变量传递完成。

结语  |  局部变量的创建、函数传参及其顺序、形参与实参的关系,函数如何调用以及如何返回。

注:不同编译器实现略有差异,但基本原理相同

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值