函数调用约定

      函数调用约定是对函数调用时如何传递参数的一种约定。、

      主要的函数调用约定有cdecl、stdcall、fastcall.

一、cdecl

      主要早C语言中使用,调用者负责处理栈。主要有两条:(1)函数参数以逆序顺序入栈(2)由调用者负责清理压入栈的函数参数。下面我们来看一个实例进行分析:

C源代码:

#include<stdio.h>
#include<stdlib.h>
long add(long a, long b)
{
long x = a, y = b;
return (x + y);
}
int main(int argc, char* argv[])
{
printf("%d\n", add(1, 2));
system("pause");
return 0;
}


      从上图中,汇编代码部分我们可以看到,参数压栈过程中,先PUSH 2再PUSH 1,这与上面的C源码是相反的;

      在CALL add后的一条指令为ADD ESP,8,add执行完之后就不需要参数1和2了,两个数共八个字节,将ESP+8就把它们从栈中清理掉了。


二、stdcall

      由被调用者负责清理压入栈的函数参数。C语言中默认的函数调用方式是cdecl,若想用stdcall,则需在定义函数时用_stdcall关键字+函数名即可。下面看一个实例:

仍然是上面的C源码,我们用关键字_stdcall

#include<stdio.h>
#include<stdlib.h>
long _stdcall add(long a, long b)
{
long x = a, y = b;
return (x + y);
}
int main(int argc, char* argv[])
{
printf("%d\n", add(1, 2));
system("pause");
return 0;
}


      从上图可以看到参数压栈仍然是逆序方式;可以看出不同于cdecl中的是在CALL add指令后没有ADD ESP,8了,那么它是怎么清理栈的呢?(下面我们看一看F7单步进入add的情况)


      上图是add函数即将调用结束的时候,可以看到图中阴影部分标识的代码RETN 8(含义为RETN+POP 8字节,RETN实现的是存储在栈中的返回地址被返回),这是下一步将要执行的指令,观察好现在ESP值,下面我们看该指令执行之后:


可以看出回到了调用add的地方。执行RENT:相当于将存储返回地址的栈空间POP,ESP+4;然后POP 8字节,将参数1,2所占的栈空间POP,ESP+8,最后我么看到在此例中ESP的值与EBP相同(因为这个例子中距离main函数的入口位置,恰好只有add的两个参数在栈中),清理栈空间结束。

      相比于cdecl,stdcall的好处在于不需要每次调用函数后都要ADD ESP,XXX的指令,代码尺寸要小一些。


三、fastcall

      顾名思义,这种调用方式的优点在于可以快速调用,如何实现快速呢?我们都知道访问寄存器的速度要快于内存。所以,fastcall通常会使用寄存器ECX、EDX来传递需要传递给函数的前两个参数,比如某函数需要四个参数,那么前两个就会放在ECX、EDX中来传递。

      这种方法的缺陷在于,可能需要额外的系统开销来管理这两个寄存器,比如当函数调用之前,这两个寄存器中就已经存着重要的数据,那么我们需要先对ECX和EDX中的值进行备份,这在函数本身就很复杂的时候就会显得很麻烦。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值