【C语言】函数栈帧的创建和销毁(详细版有图解)

目录

一. 预备知识

1.1 常见的寄存器

1.2 相关的汇编命令

1.3 内存区域的划分

 二. 函数栈帧的创建

2.1 栈帧创建及压栈的过程

 三. main函数栈帧的销毁


一. 预备知识

1.1 常见的寄存器

寄存器名称功能
esp栈顶指针
ebp栈底指针
eax累加寄存器
ebx基地址寄存器,在内存中寻址并存放基地址
ecx计数寄存器
edx多功能寄存器(多用于存放整数除法产生的余数)
esi源索引寄存器
edi目标索引寄存器
eip存放下一条指令的CPU地址

1.2 相关的汇编命令

汇编命令功能
push压栈,从栈顶增加元素
move将一个数据从原地址传送至操作地址,原地址内容不变
pop出栈,从栈顶删除元素
call将本条指令的下一条指令压入栈中,并跳转到子函数
sub从寄存器中减去数值,并将结果保存在寄存器中
add从寄存器中加上数值,然后将结果保存在寄存器中
lea将地址赋给目标寄存器
rep重复指令

1.3 内存区域的划分

在计算机中,内存划分为三个区域:栈区、堆区、静态区。其中,栈区用于存放临时变量及函数的形参,堆区用于动态分配内存,静态区用于存放全局变量、static修饰的均布变量等。

图1.1 内存空间的分配 

 二. 函数栈帧的创建

以下面的代码为例,说明函数栈帧创建的过程

#include<stdio.h>
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\n", c);
	return 0;
}

2.1 栈帧创建及压栈的过程

int main()
{
00DD19E0  push        ebp  //压栈,将ebp指向的地址存放
00DD19E1  mov         ebp,esp  //将栈顶指针指向的内容移动至栈底指针ebp,ebp指向esp
00DD19E3  sub         esp,0E4h  //将esp的地址向低位移动4位
00DD19E9  push        ebx  //压栈(基地址寄存器)
00DD19EA  push        esi  //压栈(源索引寄存器)
00DD19EB  push        edi  //压栈(目标索引寄存器)
00DD19EC  lea         edi,[ebp-24h]  //将[ebp-24h]的地址赋给edi
00DD19EF  mov         ecx,9  
00DD19F4  mov         eax,0CCCCCCCCh  
00DD19F9  rep stos    dword ptr es:[edi]  //将以edi为起点的9个地址的内容均赋为0CCCCCCCCh
00DD19FB  mov         ecx,offset _65FB160D_test@c (0DDC003h)  
00DD1A00  call        @__CheckForDebuggerJustMyCode@4 (0DD1316h)  
	int a = 10;
00DD1A05  mov         dword ptr [a],0Ah  //将a赋值为10
	int b = 20;
00DD1A0C  mov         dword ptr [b],14h  //将b赋值为20
	int c = 0;
00DD1A13  mov         dword ptr [c],0  //将c赋值为0
	c = Add(a, b);
00DD1A1A  mov         eax,dword ptr [b] //将b的值移动到eax寄存器中 
00DD1A1D  push        eax  //将b的形参压入栈中
00DD1A1E  mov         ecx,dword ptr [a] //将a的值移动到ecx寄存器中 
00DD1A21  push        ecx  //将a的形参压入栈中
00DD1A22  call        _Add (0DD13BBh)  //为Add函数开辟栈帧,跳转到Add函数
00DD1A27  add         esp,8  //返回主函数,找回栈顶地址,Add销毁,esp向高地址移动8位
00DD1A2A  mov         dword ptr [c],eax  //将寄存器eax的值(Add函数返回值)赋给c
	printf("%d\n", c);
00DD1A2D  mov         eax,dword ptr [c]  //将c的值赋给exa寄存器
00DD1A30  push        eax  //将exa压入栈中
00DD1A31  push        offset string "%d\n" (0DD7CF8h)  
00DD1A36  call        _print2 (0DD10CDh)  
00DD1A3B  add         esp,8  //栈顶指针向高地址移动8位
	return 0;
00DD1A3E  xor         eax,eax  
}

对于main函数,首先要在栈中为其本身开辟一块空间,因为要在main函数中调用add函数,在main的开辟栈帧后,还要将先后将b的形参和a的形参压入栈中。 

图2.1 main函数的压栈过程

 对于Add函数,将a和b的值放入到相应的寄存器,并压入栈中,将整个Add函数的返回结果放入eax中,Add函数销毁,对应的栈帧也销毁。

图2.2 Add函数栈帧创建的过程

 三. main函数栈帧的销毁

00DD1A40  pop         edi  //edi出栈
00DD1A41  pop         esi  //esi出栈
00DD1A42  pop         ebx  //ebx出栈
00DD1A43  add         esp,0E4h  //将栈顶指针向高地址移动OE4h位
00DD1A49  cmp         ebp,esp  
00DD1A4B  call        __RTC_CheckEsp (0DD123Fh)  
00DD1A50  mov         esp,ebp  //ebp的地址指向esp
00DD1A52  pop         ebp  //栈底指针ebp出栈
00DD1A53  ret
图3.1 main函数栈帧销毁的过程

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值