栈的删除函数c语言,C语言的函数调用过程(栈帧的创建与销毁)

从汇编的角度解析函数调用过程

看看下面这个简单函数的调用过程:

1 int Add(int x,inty)2 {3 int sum = 0;4 sum = x +y;5 returnsum;6 }7

8 intmain ()9 {10 int a = 10;11 int b = 12;12 int ret = 0;13 ret =Add(a,b);14 return 0;15 }

今天主要用汇编代码去讲述这个过程,首先介绍几个寄存器和简单的汇编指令的意思。

先看几个函数调用过程涉及到的寄存器:

(1)esp:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。

(2)ebp:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

(3)eax 是”累加器”(accumulator), 它是很多加法乘法指令的缺省寄存器。

(4)ebx 是”基地址”(base)寄存器, 在内存寻址时存放基地址。

(5)ecx 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。

(6)edx 则总是被用来放整数除法产生的余数。

(7)esi/edi分别叫做”源/目标索引寄存器”(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串.

在32位平台上,ESP每次减少4字节。

再看几条简单的汇编指令:

mov :数据传送指令,也是最基本的编程指令,用于将一个数据从源地址传送到目标地址(寄存器间的数据传送本质上也是一样的)

sub:减法指令

lea:取偏移地址

push:实现压入操作的指令是PUSH指令

pop:实现弹出操作的指令

call:用于保存当前指令的下一条指令并跳转到目标函数。

这些指令当然能看懂最好,可以让你很深刻的理解函数调用过程,不能看懂就只能通过我的描述去理解了。

进行分析之前,先来了解下内存地址空间的分布:

5937dc7180f09bc210663991ff902a3d.png

栈空间是向低地址增长的,主要是用来保存函数栈帧。 栈空间的大小很有限,仅有区区几MB大小

汇编代码实现:

main函数汇编代码:

intmain ()

{011B26E0 pushebp011B26E1 movebp,esp011B26E3 subesp,0E4h011B26E9 pushebx011B26EA pushesi011B26EB pushedi011B26EC leaedi,[ebp-0E4h]011B26F2 movecx,39h011B26F7 moveax,0CCCCCCCCh011B26FC rep stos dword ptr es:[edi]int a = 10;011B26FE movdword ptr [a],0Ahint b = 12;011B2705 movdword ptr [b],0Chint ret = 0;011B270C mov dword ptr [ret],0

ret = Add(a,b);011B2713 moveax,dword ptr [b]011B2716 pusheax011B2717 movecx,dword ptr [a]011B271A pushecx011B271B call @ILT+640(_Add) (11B1285h)011B2720 add esp,8

011B2723 mov dword ptr [ret],eax

return0;011B2726 xoreax,eax

}011B2728 popedi011B2729 popesi011B272A popebx011B272B addesp,0E4h011B2731 cmpebp,esp011B2733 call @ILT+450(__RTC_CheckEsp) (11B11C7h)011B2738 movesp,ebp011B273A popebp011B273B ret

Add函数汇编代码:

int Add(int x,inty)

{011B26A0 pushebp011B26A1 movebp,esp011B26A3 subesp,0CCh011B26A9 pushebx011B26AA pushesi011B26AB pushedi011B26AC leaedi,[ebp-0CCh]011B26B2 movecx,33h011B26B7 moveax,0CCCCCCCCh011B26BC rep stos dword ptr es:[edi]int sum = 0;011B26BE mov dword ptr [sum],0sum = x + y;011B26C5 moveax,dword ptr [x]011B26C8 addeax,dword ptr [y]011B26CB movdword ptr [sum],eax

return sum;011B26CE moveax,dword ptr [sum]

}011B26D1 popedi011B26D2 popesi011B26D3 popebx011B26D4 movesp,ebp011B26D6 popebp011B26D7 ret

下面图中详细描述了调用过程地址变化(此处所有地址是取自32位windows系统vs编辑器下的调试过程。):

10ac61c6249b93c42e2ab97d1f71ee8c.png

过程描述:

1、参数拷贝(参数实例化)。

2、保存当前指令的下一条指令,并跳转到被调函数。

这些操作均在main函数中进行。

接下来是调用Add函数并执行的一些操作,包括:

1、移动ebp、esp形成新的栈帧结构。

2、压栈(push)形成临时变量并执行相关操作。

3、return一个值。

这些操作在Add函数中进行。

被调函数完成相关操作后需返回到原函数中执行下一条指令,操作如下:

1、出栈(pop)。

2、回复main函数的栈帧结构。(pop )

3、返回main函数

这些操作也在Add函数中进行。 至此,在main函数中调用Add函数的整个过程已经完成。

总结起来整个过程就三步:

1)根据调用的函数名找到函数入口;

2)在栈中审请调用函数中的参数及函数体内定义的变量的内存空间

3)函数执行完后,释放函数在栈中的审请的参数和变量的空间,最后返回值(如果有的话)

如果你学了微机原理,你会想到cpu中断处理过程,是的,函数调用过程和中断处理过程一模一样。

函数调用约定:

这里再补充一下各种调用规定的基本内容。

_stdcall调用约定

所有参数按照从右到左压入堆栈,由被调用的子程序清理堆栈

_cdecl调用约定(The C default calling convention,C调用规定)

参数也是从右到左压入堆栈,但由调用者清理堆栈。

_fastcall调用约定

顾名思义,_fastcall的目的主要是为了更快的调用函数。它主要依靠寄存器传递参数,剩下的参数依然按照从右到左的顺序压入堆栈,并由被调用的子程序清理堆栈。

本篇博文是按调用约定__stdcall 调用函数。

内容来源于网络如有侵权请私信删除

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值