函数调用过程分析

本文详细介绍了在Windows环境下,编译器如何利用堆栈进行函数调用及参数传递的过程。包括函数调用前后堆栈的状态变化、实参与形参的对应关系,以及不同调用约定的特点。

编译器一般使用堆栈实现函数调用,Windows为每个线程维护一个堆栈,堆栈大小可以设置,编译器可以使用堆栈存放每个函数参数,局部变量等。由于函数调用经常会被嵌套,故同一时刻,堆栈中存储多个函数的信息,每个函数会占一个连续区域,一个函数占用的区域称为帧,编译器是从高地址开始使用堆栈,在多线程任务下,CPU的堆栈指针指向的存储器区域就是当前使用的堆栈。切换线程时,把堆栈指针设为当前线程所在堆栈的栈顶地址。具体而言,函数堆栈用于:

1 进入函数前,保存返回地址和环境变量;

将函数调用语句下一条语句的地址保存到在栈中,对实参表从后向前,一次计算出实参的值,并且将值压栈,跳转到函数体处。

2 进入函数后,保存实參或者局部变量

如果函数体中定义了变量,将变量压栈,将每一个形参以栈中对应的实参值取代,执行函数体的功能体。将函数体中的变量、保存到栈中的实参值,依次从栈中取出,释放栈空间>

返回过程执行的是函数体中的return语句。其过程是从栈中取出刚开始调用函数时压入的地址,跳转到函数的下一条语句。

下面举例:

#include <stdio.h>
int func(int param1 ,int param2,int param3)
{
        int var1 = param1;
        int var2 = param2;
        int var3 = param3;
 
        printf("var1=%d,var2=%d,var3=%d",var1,var2,var3);
        return var1;
}
 
int main(int argc, char* argv[])
{
        int result = func(1,2,3);//×调用
 
        return 0; 
}

注意:EBP是指向栈底的指针,在过程调用中不变,又称为帧指针。ESP指向栈顶,程序执行时移动,ESP减小分配空间,ESP增大释放空间,ESP又称为栈指针

1 函数Main执行,main各个参数从右到左逐步入栈,最后压入返回地址

2 执行×处调用,3个参数从右到左压入堆栈,栈内分布:


3 然后是返回地址入栈:


4 通过跳转指令进入被调用函数后,EBP入栈,然后把当前ESP的值给EBP,对应的汇编指令:

push ebp
mov ebp esp

5 开始执行赋值:

mov 0x8(%ebp), %eax
mov %eax, -0x4(%ebp)
把[ebp+0x8]地址里的内容赋值给eax,即把param1的值赋值给eax,再把eax的值放到[ebp-4]这个地址即把eax值赋值var1这样完成 int var1 = param1,其他变量雷同。


6 输出结构后最后return 1,其汇编:

mov -0x4(%ebp) %eax
通过eax寄存器保存返回值

7 函数调用完毕,局部变量var3,var2,var1一次出栈,EBP恢复原值,返回地址出栈,找到原执行地址,param1,param2,param3依次出栈,函数调用执行完毕。

附录:WIN32下的4种调用:

a _cdecl,它是C/C++默认调用方式,实參是以参数列表从右到左依次入栈,函数堆栈由调用方维护,主要应用于带有可变参数的函数上

b _stdcalll,是WIN API的调用约定,实參是以参数列表从右到左依次入栈,函数堆栈由调用方维护,但是带有可变参数的函数上即使显示指定_stdcall编译器也会自动改成c_cdecl

c _thiscall,是类的非静态成员函数的调用约定,不能在含有可变参数的函数上否则编译出错,实參是以参数列表从右到左依次入栈,函数堆栈由调用方维护,含有this指针,不是存放在函数堆栈而是CPU寄存器

d _fastcall,它的实參不是存放在函数堆栈而是CPU寄存器,所以不需要入栈出栈,堆栈释放,可以快速调用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值