一、函数几种调用约定
调用约定 | 参数压栈顺序 | 平衡堆栈 |
__cdecl | 从右至左入栈 | 调用者清理栈 |
__stdcall | 从右至左入栈 | 自身清理堆栈 |
__fastcall | ECX/EDX传送前两个 | 自身清理堆栈 |
剩下:从右至左入栈 |
用法:返回值类型 (调用约定)函数名()
不写调用约定的话,默认第一种调用约定,就是我们平常用的函数调用
下面举例说明下:
1、int __cdecl Plus(int a, int b)
{
return a+b;
}
对应汇编代码:
push 2
push 1
call @ILT+15(Plus) (00401014)
add esp,8 ;这个就是外面平衡堆栈,传入两个参数,且为int型4byte,所以是8
2、int __stdcall Plus(int a, int b)
{
return a+b;
}
对应汇编代码:
push 2
push 1
call @ILT+10(Plus) (0040100f)
函数内部:
ret 8 ;这句是call对应的,内平栈
3,4都属于第三种情况
3、int __fastcall Plus(int a, int b)
{
return a+b;
}
mov edx,2 ;因为传入寄存器不需要平衡堆栈
mov ecx,1
call @ILT+0(Plus) (00401005)
函数内部:
ret
4、int __fastcall Plus4(int a, int b,int c,int d)
{
return a+b+c+d;
}
push 4 ;当参数大于两个时,编译器会倒着存入堆栈,剩两个存入寄存器里
push 3
mov edx,2
mov ecx,1
call @ILT+5(Plus) (0040100a)
函数内部:
ret 8 ;外平栈,平衡传入堆栈的那一部分
二、细节之处:
1、看下面代码有没有发现什么异常
#include<stdio.h>
char fun(char x,char y)
{
return x+y;
}
int main()
{
char s=fun(2,3);
return 0;
}
对应汇编代码(主要的部分):
push 3
push 2
call fun (01213DEh)
add esp,8 ;这里平衡堆栈的时候也是8个字节,但是我们传入的明明是两个char类型
这里发现传入char类型和传入int类型(参考调用约定第一个例子)的压入堆栈都是按照4byte压入的,short我不测试了,也是按照4byte压入堆栈的,这是什么原理?
因为本机原理(参考了这位兄台的博客https://www.cnblogs.com/zimudao/p/8505530.html)
在32位的系统中,系统默认最合适的数据类型,就是32个bit,即4字节。同理,64位的系统就是8字节。
也就是说,如果使用小于4个字节的局部变量来进行参数传递,vs编译器仍然会按照4个字节来进行传递,但是多余的部分并不会使用。
我们可以看看 long long传入的时候是怎么传入的
#include<stdio.h>
long long fun(long long x, long long y)
{
return x + y;
}
int main()
{
long long s = fun(2, 3);
return 0;
}
对应汇编:
push 0
push 3
push 0
push 2
call fun (013513E3h)
push ebp
mov ebp,esp
sub esp,0C0h
push ebx
push esi
push edi
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
mov eax,dword ptr [x]
add eax,dword ptr [y]
mov edx,dword ptr [ebp+0Ch]
adc edx,dword ptr [ebp+14h]
pop edi
pop esi
pop ebx
add esp,0C0h
cmp ebp,esp
mov esp,ebp
pop ebp
ret
add esp,10h
mov dword ptr [s],eax
这时候发现传入了2个参数,可是压入堆栈一共压入了4个,
因为long long8字节,所以正好两个地址存一个long long类型的,上面的存高位,下面的存低位。