__cdecl 调用约定不适用于 msvc x64

sbw*_*sbw 3 c++ windows calling-convention visual-c++

__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;
}
Run Code Online (Sandbox Code Playgroud)

我用来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
...
Run Code Online (Sandbox Code Playgroud)

您可以看到参数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
...
Run Code Online (Sandbox Code Playgroud)

参数通过寄存器传递,edxecx不是通过堆栈传递。

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

Fir*_*cer 6

x64 有它自己的调用约定。

\n\n

微软文档__cdecl

\n\n
\n

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

\n
\n\n

Microsoft docs x64 calling convention

\n\n
\n

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

\n\n

...

\n\n

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

\n
\n\n

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

\n\n

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

\n