关于x86下的几种调用约定的一点测试 | Blurred code

关于x86下的几种调用约定的一点测试

2023/04/16

LastMod:2023/04/16

Categories: cpp

写了个简单的例子来测试 _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;
}

结论:

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的符号混合也不一样。

009 00000000 SECT3  notype ()    External     | _add
00A 00000010 SECT3  notype ()    External     | _add2@36
00B 00000020 SECT3  notype ()    External     | @add3@12