__cdecl 调用约定不适用于 msvc x64

__cdecl calling convention not work on msvc x64

只是 __cdecl 调用约定的测试。

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

#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 环境中做同样的事情,我应该怎么做。

x64 有自己的调用约定。

Microsoft docs __cdecl

On ARM and x64 processors, __cdecl is accepted but typically ignored by the compiler. By convention on ARM and x64, arguments are passed in registers when possible, and subsequent arguments are passed on the stack. In x64 code, use __cdecl to override the /Gv compiler option and use the default x64 calling convention.

Microsoft docs x64 calling convention

The x64 Application Binary Interface (ABI) uses a four-register fast-call calling convention by default. Space is allocated on the call stack as a shadow store for callees to save those registers. There's a strict one-to-one correspondence between the arguments to a function call and the registers used for those arguments. Any argument that doesn’t fit in 8 bytes, or isn't 1, 2, 4, or 8 bytes, must be passed by reference.

...

Integer arguments are passed in registers RCX, RDX, R8, and R9

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

__stdcall, __fastcall and __thiscall are also ignored. __vectorcall 可用(/Gv 开关使其成为默认值)并且是另一个寄存器调用约定,但与 x64 默认值相比它可以在更多情况下使用寄存器并且有一些其他规则差异。