首页 > 编程知识 正文

c语言函数调用顺序,C语言请简述方法调用的过程

时间:2023-05-06 17:39:24 阅读:135885 作者:915

今天突然看到有人私信我说一直没写函数调用过程(栈帧的形成和销毁过程)这篇博文,赶紧补上。

对于刚看到的堆栈帧的内容,我感到困惑。 我认为堆栈帧的创建和销毁很麻烦。 几句话完全解释不完,而且我好像不能把他的过程解释清楚,所以在博文中遇到函数调用就避开了。 现在开始写堆栈帧调用过程,其实我觉得这个过程并不太难(但还是抽象的,毕竟我们不太清楚计算机基础是如何工作的)

创建堆栈帧的销毁过程示例代码:

intadd(intx,int y ) { int sum=0; sum=x y; return sum; (}int main ) ) { int a=10; int b=12; int ret=0; ret=add(a,b ); 返回0; }今天主要用汇编代码来说明这个过程。 首先,介绍几个寄存器和简单的汇编指令的含义。

首先,让我们看一下与一些函数调用过程相关的寄存器。

)1) esp )堆栈指针寄存器(extended stack pointer )在存储器中放置总是指向系统堆栈顶部的堆栈帧的堆栈顶部的指针。

2 ) ebp )基本地址指针寄存器(extended base pointer )总是将指向系统堆栈最上面的堆栈帧的底部的指针放在存储器中。

)3) eax是累加器,其是许多加法乘法指令的默认寄存器。

)4) ebx是“基地址”(base )寄存器,在指定存储器地址时存储基地址。

5 ) ecx为计数器(counter ),重复前缀指令和循环指令)的内定计数器。

)6) edx总是用于加入整数除法产生的余数。

) esi/edi分别被称为“源/目标寄存器”。 因为在许多字符串操作命令中,DS:ESI指定源字符串,而ES:EDI指定目标字符串。

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

让我们看几个简单的汇编命令:

mov )数据传送指令,也是用于从源地址向目的地地址传送数据的最基本的编程指令((寄存器间的数据传送本质上也相同) )。

sub :减法命令

lea :取偏移地址

推式:用于实现按下操作的命令是推式命令

pop :用于实现弹出操作的命令

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

这些指令当然最好能理解,可以让您深入理解函数调用的过程。 如果不能理解的话,只能通过我的说明来理解。

进行分析之前,请确认存储器地址空间的分布。

堆栈区域增加到低地址,主要用于保存函数堆栈帧。 堆栈空间的大小仅限于几MB

汇编代码实现:

主函数汇编代码:

int main (011 b 26 E0 push ebp 011 b 26 E1 mov ebp,esp 011B26E3 sub esp,0e4h 011 b 26 e9push ebx 011 b 26 eapushesi 011 b 26 ebpushedi 011 b 26 EC lea [ EC 39h 011B26F7 mov eax,0 cccccccccccccch 011 b 26 fcrepstosdwordptres : [ EDI ] inta=10; 011B26FE mov dword ptr [a],0Ah int b=12; 011B2705 mov dword ptr [b],0Ch int ret=0; 011B270C mov dword ptr [ret],0ret=add(a,b ); 011B2713 mov eax,dword ptr [ b ] 011 b 2716 push eax 011 b 2717 mov ecx,dword ptr [ a ] 011 b 271 apushecx 011 b 271 BC all @ ILT 640 (add ) ) 011B2726 xor eax,eax } 011 b 2728 popedi 011 b 2729 popesi 011 b 272 apop ebx 011 b 272 bad desp,0E4h 011B2731 cmp ebp,ESP011b2733call@ILT4553

011B273A pop ebp 011B273B ret

Add函数汇编代码:

int Add(int x,int y){011B26A0 push ebp 011B26A1 mov ebp,esp 011B26A3 sub esp,0CCh 011B26A9 push ebx 011B26AA push esi 011B26AB push edi 011B26AC lea edi,[ebp-0CCh] 011B26B2 mov ecx,33h 011B26B7 mov eax,0CCCCCCCCh 011B26BC rep stos dword ptr es:[edi] int sum = 0;011B26BE mov dword ptr [sum],0 sum = x + y;011B26C5 mov eax,dword ptr [x] 011B26C8 add eax,dword ptr [y] 011B26CB mov dword ptr [sum],eax return sum;011B26CE mov eax,dword ptr [sum] }011B26D1 pop edi 011B26D2 pop esi 011B26D3 pop ebx 011B26D4 mov esp,ebp 011B26D6 pop ebp 011B26D7 ret

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

过程描述:
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调用约定

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

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

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。