__cdecl调用约定在msvc x64上不起作用

时间:2019-04-10 12:07:12

标签: c++ windows visual-c++ calling-convention

仅仅是测试__cdecl调用约定。

这是一个cmake项目,只有一个源文件:

#include <stdio.h>

#define CALL_CONVENTION __cdecl

void CALL_CONVENTION f(int a, int b)
{
    printf("%d, %d", a, b);
}

int main()
{
    f(1, 2);

    return 0;
}

我正在使用set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /FA")输出汇编代码。

当我使用cmake -G "Visual Studio 15"进行构建时,它正在构建32位应用程序,并且所有事情都在预期之中:

...
; Line 12
    push    ebp
    mov ebp, esp
; Line 13
    push    2        ; <------- argument 2
    push    1        ; <------- argument 1
    call    _f       ; <------- call function
    add esp, 8
; Line 15
    xor eax, eax
; Line 16
    cmp ebp, esp
    call    __RTC_CheckEsp
    pop ebp
    ret 0
_main   ENDP
...

您可以看到参数由push 2push 1指令传递,这是__cdecl的调用约定。

但是,如果我使用cmake -G "Visual Studio 15 Win64"来构建64位应用程序,则__cdecl注释似乎不起作用(参数未通过堆栈传递):

...
; Line 12
$LN3:
    push    rdi
    sub rsp, 32                 ; 00000020H
    mov rdi, rsp
    mov ecx, 8
    mov eax, -858993460             ; ccccccccH
    rep stosd
; Line 13
    mov edx, 2        ; <------ argument 2
    mov ecx, 1        ; <------ argument 1
    call    f         ; <------ call function
; Line 15
    xor eax, eax
; Line 16
    add rsp, 32                 ; 00000020H
    pop rdi
    ret 0
main    ENDP
...

参数由寄存器edxecx传递,而不是由堆栈传递。

那么,即使我指定了__cdecl,为什么参数也不在x64中通过堆栈传递?如果我想在x64环境中执行相同的操作,该怎么办。

1 个答案:

答案 0 :(得分:3)

x64具有自己的调用约定。

Microsoft docs __cdecl

  

在ARM和x64处理器上,__cdecl被接受,但通常被编译器忽略。根据ARM和x64的约定,参数在可能的情况下在寄存器中传递,而后续参数在堆栈上传递。在x64代码中,使用__cdecl覆盖/ Gv编译器选项,并使用默认的x64调用约定。

Microsoft docs x64 calling convention

  

默认情况下,x64应用程序二进制接口(ABI)使用四寄存器快速调用调用约定。在调用堆栈上分配了空间作为影子存储,供被调用者保存这些寄存器。函数调用的参数与用于这些参数的寄存器之间存在严格的一对一对应关系。任何不适合8个字节,或者不是1、2、4或8个字节的参数都必须通过引用传递。

     

...

     

整数参数在寄存器RCX,RDX,R8和R9中传递

对于int aint b,您可以使用ECX和EDX看到它(它们是32位,而完整的RCX和RDX是64位)。

__stdcall__fastcall__thiscall也将被忽略。 __vectorcall是可用的(/ Gv开关将其设置为默认值),并且是另一种调用寄存器的约定,但是与x64默认值相比,它可以在更多情况下使用寄存器,并且还有一些其他规则差异。