函数调用约定


  在C程序中,带参数的函数调用有几种方式;这里主要介绍三种:stdcall, cdecl, fastcall; 

   stdcallWINDOWS API的默认方式;这种方式,参数由被调用者清除,因此产生的代码体略微小一点点(不明显,毕竟就那么几条汇编指令)

但该方式不能应付变长参数的函数,比如printf;因为printf本身不知道传进来多少个参数,参数大小,只能运行时解析获得;而对于void  test(int a, int b); test函数编译时就知道参数一定是两个int,所以test能自行清理参数。

   CdeclC默认的函数调用约定;它和上者的区别是,由调用者清除参数;所以它能处理printf族函数,调用者当然知道传进去的参数大小。

   Fastcall是尽量将参数用寄存器传递而不是入栈;好像使用的不多;简单测了一个计算两数和的函数,速度甚至比cdecl之类的慢。而且只能使用两个寄存器,比如说你有5个参数,那么只有前两个参数压入寄存器。

   几种方式无所谓好与坏,主要是看是否变长参数调用,还有调第三方库时要遵循约定。C默认是cdeclWindows函数基本默认都是WINAPI,也就是__stdcall

   下面给出代码,以更好的明白这几种方式。代码很简单,main调用一个add函数,计算两数之和;

STDCALL

int __stdcall Add(int a, int b) 

{

008B13B0  push        ebp  

008B13B1  mov         ebp,esp 

008B13B3  sub         esp,0CCh 

008B13B9  push        ebx  

008B13BA  push        esi  

008B13BB  push        edi  

008B13BC  lea         edi,[ebp-0CCh] 

008B13C2  mov         ecx,33h 

008B13C7  mov         eax,0CCCCCCCCh 

008B13CC  rep stos    dword ptr es:[edi] 

    int c = a + b;

008B13CE  mov         eax,dword ptr [a] 

008B13D1  add         eax,dword ptr [b] 

008B13D4  mov         dword ptr [c],eax 

    return  c;

008B13D7  mov         eax,dword ptr [c] 

}

008B13DA  pop         edi  

008B13DB  pop         esi  

008B13DC  pop         ebx  

008B13DD  mov         esp,ebp 

008B13DF  pop         ebp  

008B13E0  ret           8     // Add自己清除参数,8个字节,也就是两个int

int main()

{

008B1430  push        ebp  

008B1431  mov         ebp,esp 

008B1433  sub         esp,0D8h 

008B1439  push        ebx  

008B143A  push        esi  

008B143B  push        edi  

008B143C  lea         edi,[ebp-0D8h] 

008B1442  mov         ecx,36h 

008B1447  mov         eax,0CCCCCCCCh 

008B144C  rep stos    dword ptr es:[edi] 

    int aa = 7, bb = 9;

008B144E  mov         dword ptr [aa],7 

008B1455  mov         dword ptr [bb],9 

    Add(aa, bb);

008B145C  mov         eax,dword ptr [bb] 

008B145F  push        eax  

008B1460  mov         ecx,dword ptr [aa] 

008B1463  push        ecx  // 调用者main负责参数入栈,都是这样的,除了FASTCALL

008B1464  call        Add (8B11EAh) // 调用完了main什么也不敢,Add自己清除堆栈参数

    return  0;

008B1469  xor         eax,eax 

}

CDECL

int __cdecl Add(int a, int b) 

{

001213B0  push        ebp  

001213B1  mov         ebp,esp 

001213B3  sub         esp,0CCh 

001213B9  push        ebx  

001213BA  push        esi  

001213BB  push        edi  

001213BC  lea         edi,[ebp-0CCh] 

001213C2  mov         ecx,33h 

001213C7  mov         eax,0CCCCCCCCh 

001213CC  rep stos    dword ptr es:[edi] 

    int c = a + b;

001213CE  mov         eax,dword ptr [a] 

001213D1  add         eax,dword ptr [b] 

001213D4  mov         dword ptr [c],eax 

    return  c;

001213D7  mov         eax,dword ptr [c] 

}

001213DA  pop         edi  

001213DB  pop         esi  

001213DC  pop         ebx  

001213DD  mov         esp,ebp 

001213DF  pop         ebp  

001213E0  ret     // 被调用者Add不管参数,直接返回        

int main()

{

00121430  push        ebp  

00121431  mov         ebp,esp 

00121433  sub         esp,0D8h 

00121439  push        ebx  

0012143A  push        esi  

0012143B  push        edi  

0012143C  lea         edi,[ebp-0D8h] 

00121442  mov         ecx,36h 

00121447  mov         eax,0CCCCCCCCh 

0012144C  rep stos    dword ptr es:[edi] 

    int aa = 7, bb = 9;

0012144E  mov         dword ptr [aa],7 

00121455  mov         dword ptr [bb],9 

Add(aa, bb); // 调用者main负责入站,和STDCALL一样

0012145C  mov         eax,dword ptr [bb] 

0012145F  push        eax  

00121460  mov         ecx,dword ptr [aa] 

00121463  push        ecx  

00121464  call        Add (1211EFh) 

00121469  add         esp,8  // Add函数返回后,main执行栈清除,以适用于变长参数的函数

    return  0;

0012146C  xor         eax,eax 

}          

FASTCALL

 int __fastcall Add(int a, int b) 

{

001613B0  push        ebp  

001613B1  mov         ebp,esp 

001613B3  sub         esp,0E4h 

001613B9  push        ebx  

001613BA  push        esi  

001613BB  push        edi  

001613BC  push        ecx  

001613BD  lea         edi,[ebp-0E4h] 

001613C3  mov         ecx,39h 

//011B144E  mov         byte ptr [ebp-0E9h],0 这句话在有 未初始化变量时产生,是个标志

001613C8  mov         eax,0CCCCCCCCh // 将局部变量区域初始化成0XCC

// 如果局部变量没有初始化,会执行断点中断指令,因为0XCC就是int 3指令码。

// 这是VS DEBUG一个特性,预防使用未初始化的变量。它会插入这样的代码:

// 011B1473  cmp         byte ptr [ebp-0E9h],0 

//011B147A  jne         main+59h (11B1489h)  相等则有问题

//011B147C  push        offset  (11B14BAh) 

//011B1481  call        @ILT+175(__RTC_UninitUse) (11B10B4h) 

//011B1486  add         esp,4 

001613CD  rep stos    dword ptr es:[edi] 

001613CF  pop         ecx  

001613D0  mov         dword ptr [ebp-14h],edx 

001613D3  mov         dword ptr [ebp-8],ecx 

    int c = a + b;

001613D6  mov         eax,dword ptr [a] 

001613D9  add         eax,dword ptr [b] 

001613DC  mov         dword ptr [c],eax 

    return  c;

001613DF  mov         eax,dword ptr [c] 

}

001613E2  pop         edi  

001613E3  pop         esi  

001613E4  pop         ebx  

001613E5  mov         esp,ebp 

001613E7  pop         ebp  

001613E8  ret // 这里会做栈的清除(如果有,参数多的话不能全入寄存器仍然是由Add处理,因此该方式也不适用于printf; 本例参数太少,没有入栈的参数。

int main()

{

00161430  push        ebp  

00161431  mov         ebp,esp 

00161433  sub         esp,0D8h 

00161439  push        ebx  

0016143A  push        esi  

0016143B  push        edi  

0016143C  lea         edi,[ebp-0D8h] 

00161442  mov         ecx,36h 

00161447  mov         eax,0CCCCCCCCh 

0016144C  rep stos    dword ptr es:[edi] 

    int aa = 7, bb = 9;

0016144E  mov         dword ptr [aa],7 

00161455  mov         dword ptr [bb],9 

    Add(aa, bb);

0016145C  mov         edx,dword ptr [bb] 

0016145F  mov         ecx,dword ptr [aa]  // MAIN将两个参数放入寄存器而不是入栈

00161462  call        Add (1611E5h)  

    return  0;

00161467  xor         eax,eax 

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值