c++ 为什么x64中忽略了__stdcall调用约定?

ne5o7dgx  于 2023-08-09  发布在  其他
关注(0)|答案(1)|浏览(169)

我知道__cdecl__stdcall之间的区别是什么,但我不太清楚为什么x64构建中的编译器会忽略__stdcall
下面代码中的函数

int __stdcall stdcallFunc(int a, int b, int c, int d, int e, int f, int g)
{
    return a + b + c + d + e + f + g;
}

int __cdecl cdeclFunc(int a, int b, int c, int d, int e, int f, int g)
{
    return a + b + c + d + e + f + g;
}

int main()
{
    stdcallFunc(1, 2, 3, 4, 5, 6, 7);
    cdeclFunc(1, 2, 3, 4, 5, 6, 7);

    return 0;
}

字符串
有足够的参数超出可用的CPU寄存器。因此,某些参数必须通过堆栈传递。我对汇编并不熟练,但我注意到了x86和x64汇编之间的一些差异。

x64个

main    PROC
$LN3:
        sub     rsp, 72                             ; 00000048H
        mov     DWORD PTR [rsp+48], 7
        mov     DWORD PTR [rsp+40], 6
        mov     DWORD PTR [rsp+32], 5
        mov     r9d, 4
        mov     r8d, 3
        mov     edx, 2
        mov     ecx, 1
        call    ?stdcallFunc@@YAHHHHHHHH@Z          ; stdcallFunc
        mov     DWORD PTR [rsp+48], 7
        mov     DWORD PTR [rsp+40], 6
        mov     DWORD PTR [rsp+32], 5
        mov     r9d, 4
        mov     r8d, 3
        mov     edx, 2
        mov     ecx, 1
        call    ?cdeclFunc@@YAHHHHHHHH@Z                ; cdeclFunc
        xor     eax, eax
        add     rsp, 72                             ; 00000048H
        ret     0
main    ENDP

x86个

_main   PROC
        push    ebp
        mov     ebp, esp
        push    7
        push    6
        push    5
        push    4
        push    3
        push    2
        push    1
        call    ?stdcallFunc@@YGHHHHHHHH@Z          ; stdcallFunc
        push    7
        push    6
        push    5
        push    4
        push    3
        push    2
        push    1
        call    ?cdeclFunc@@YAHHHHHHHH@Z                ; cdeclFunc
        add     esp, 28                             ; 0000001cH
        xor     eax, eax
        pop     ebp
        ret     0
_main   ENDP


1.如预期的那样,前4个参数通过x64中的寄存器传递。
1.其余的参数以与x86中相同的顺序放在堆栈上。
1.与x86相反,在x64中我们不使用push指令。相反,我们在main的开头保留足够的堆栈空间,并使用mov指令将参数添加到堆栈中。
1.在x64中,在两个call之后都不会进行堆栈清除,但会在main的结尾进行。
这就引出了我的问题:
1.为什么x64使用mov而不是push?我想它只是更高效,在x86中不可用。
1.为什么x64中的call指令之后没有堆栈清理?
1.微软选择忽略x64汇编中的__stdcall的原因是什么?从docs
在ARM和x64处理器上,编译器接受并忽略__stdcall
Here是示例代码和程序集。

6g8kf2rb

6g8kf2rb1#

1.为什么x64使用mov而不是push?我想它只是更高效,在x86中不可用。
不是因为这个。这两种指令也存在于x86汇编语言中。
编译器没有为x64代码发出push指令的原因可能是因为它必须直接调整堆栈指针,以便为调用的函数创建32字节的“影子空间”。请参阅此链接(由@NateEldredge提供)以获取有关“阴影空间”的更多信息。
push指令分配32字节的“影子空间”将需要4条64位push指令,但只需要一条sub指令。这就是为什么它更喜欢使用sub指令。由于它使用sub指令来创建32字节的影子空间,因此将sub指令的操作数从32更改为72并没有任何损失,这将在堆栈上分配72字节的内存,这足以在堆栈上传递3个参数(其他4个参数在CPU寄存器中传递)。
我不明白为什么它在堆栈上分配了72个字节,但是,根据我的计算,它只需要56个字节(32个字节的“影子空间”和24个字节的3个参数在堆栈上传递)。编译器可能会为局部变量或异常处理保留额外的16个字节,这些字节可能会在编译器优化活动时被优化掉。
1.为什么x64中调用指令后没有堆栈清理?
在调用指令之后有堆栈清理。这是什么线
add rsp, 72
是的
但是,由于某些原因(可能是提高了性能),x64编译器只在调用函数结束时执行清理,而不是在每个函数调用之后执行清理。这意味着在x64编译器中,所有函数调用都为它们的参数共享相同的堆栈空间,而在x86编译器中,每次函数调用时都会分配和清理堆栈空间。
1.微软选择在x64汇编中忽略__stdcall的原因是什么?
关键字_stdcall_cdecl指定32位调用约定。这就是为什么它们与64位程序无关(即在x64上,只有标准调用约定和扩展的__vectorcall调用约定。

相关问题