写了个简单的例子来测试 _cdecl
,_stdcall
,_fastcall
的区别,见https://godbolt.org/z/ao9v3Tj7Y
int __cdecl add(int a,int b,...)
{
return a + b;
}
int __stdcall add2(int a,int b,int,int,int,int,int,int,int)
{
return a + b;
}
int __fastcall add3(int a,int b,int c)
{
return a + b + c;
}
int main()
{
int _ = add(1,2,3,4,5,6,7);
_ = add2(1,2,3,4,5,6,7,8,9);
_ = add3(1,2,3);
return 0;
}
结论:
- x64下都是走
fastcall
。即使手动加了标识符,也是走fastcall
- x86下不加标识符的情况下,走`__cdeclc
- 不同的调用约定有不同的name mangling方式。
int __cdecl add(int a,int b,...)
{
return a + b;
}
int __stdcall add2(int a,int b,int,int,int,int,int,int,int)
{
return a + b;
}
int _ = add(1,2,3,4,5,6,7);
_ = add2(1,2,3,4,5,6,7,8,9);
编译出来的x86汇编可以看:
; __cdecl调用的时候,所有参数压栈,并且call完以后,编译器会插入` add esp xxx`来清栈,函数体内部ret 0
push 7
push 6
push 5
push 4
push 3
push 2
push 1
call int add(int,int,...) ; add
add esp, 28 ; 0000001cH
mov DWORD PTR __$[ebp], eax
; __stdcall是在函数调用的最后会插入ret XXX的指令,函数内部返回的时候清栈
push 9
push 8
push 7
push 6
push 5
push 4
push 3
push 2
push 1
call int add2(int,int,int,int,int,int,int,int,int) ; add2
mov DWORD PTR __$[ebp], eax
fastcall是x64的默认调用约定,前4个参数用寄存器传参,后面的压栈,函数体ret清栈。
C++的name mangling规则不受calling convention影响,MSVC的都是以 ?开头
009 00000000 SECT3 notype () External | ?add@@YAHHHZZ (int __cdecl add(int,int,...))
00A 00000010 SECT3 notype () External | ?add2@@YGHHHHHHHHHH@Z (int __stdcall add2(int,int,int,int,int,int,int,int,int))
00B 00000020 SECT3 notype () External | ?add3@@YIHHHH@Z (int __fastcall add3(int,int,int))
在给函数声明加上extern C
,要求以C的规则导出符号,不同的calling convention
的符号混合也不一样。
cdecl
模式下不会有name mangling
stdcall
的name mangling
规则是后面会跟一个@x,其中x是参数的字节数(因为stdcall/fastcall
不支持变参)fastcall
前面的symbol是@开头
009 00000000 SECT3 notype () External | _add
00A 00000010 SECT3 notype () External | _add2@36
00B 00000020 SECT3 notype () External | @add3@12