为什么在 x64 中忽略 __stdcall 调用约定?
Why is the __stdcall calling convention ignored in x64?
我知道 __cdecl
和 __stdcall
之间的区别是什么,但我不太确定为什么 __stdcall
在 x64 构建中被编译器忽略。
以下代码中的函数
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
- 正如预期的那样,前 4 个参数在 x64 中通过寄存器传递。
- 其余参数按与 x86 中相同的顺序放入堆栈。
- 与 x86 相反,在 x64 中我们不使用
push
指令。相反,我们在 main
的开头保留足够的堆栈 space 并使用 mov
指令将参数添加到堆栈。
- 在 x64 中,在
call
之后没有堆栈清理发生,但是在 main
. 结束时
这让我想到了我的问题:
- 为什么 x64 使用
mov
而不是 push
?我认为它只是更有效率并且在 x86 中不可用。
- 为什么在 x64 中
call
指令后没有堆栈清理?
- Microsoft 选择在 x64 程序集中忽略
__stdcall
的原因是什么?
来自 docs:
On ARM and x64 processors, __stdcall is accepted and ignored by the compiler
Here 是示例代码和程序集。
- Why does x64 use
mov
rather than push
? I assume it's just more efficient and wasn't available in x86.
不是这个原因。这两条指令也存在于 x86 汇编语言中。
您的编译器没有为 x64 代码发出 push
指令的原因可能是因为它无论如何都必须直接调整堆栈指针,以创建 32 字节的“阴影 space " 用于被调用的函数。有关“影子 space”的更多信息,请参阅 this link(由 @NateEldredge 提供)。
使用 push
指令分配 32 个字节的“shadow space”将需要 4 个 64 位 push
指令,但只需要一个 sub
指令。这就是为什么它更喜欢使用 sub
指令。由于它无论如何都使用 sub
指令来创建 32 字节的影子 space,因此将 sub
指令的操作数从 32 更改为 72 没有任何惩罚,它分配了 72 字节的影子堆栈上的内存,足以在堆栈上传递 3 个参数(其他 4 个在 CPU 寄存器中传递)。
我不明白为什么它在堆栈上分配 72 个字节,但是,根据我的计算,它只需要 56 个字节(32 个字节的“shadow space”和 24 个在堆栈上传递的 3 个参数的字节)。编译器可能会为局部变量或异常处理保留额外的 16 个字节,当编译器优化处于活动状态时,这些字节可能会被优化掉。
- Why is there no stack cleanup after the call instructions in x64?
调用指令后有堆栈清理。这条线是这样的
add rsp, 72
会。
但是,由于某些原因(可能是为了提高性能),x64 编译器仅在调用函数结束时执行清理,而不是在每次函数调用之后执行清理。这意味着对于 x64 编译器,所有函数调用为其参数共享相同的堆栈 space,而对于 x86 编译器,堆栈 space 在每次函数调用时分配和清理。
- What's the reason that Microsoft chose to ignore __stdcall in x64 assembly?
关键字 _stdcall
和 _cdecl
指定 32 位调用约定。这就是它们与 64 位程序(即 x64)无关的原因。在 x64 上,只有 standard calling convention and the extended __vectorcall
调用约定。
我知道 __cdecl
和 __stdcall
之间的区别是什么,但我不太确定为什么 __stdcall
在 x64 构建中被编译器忽略。
以下代码中的函数
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
- 正如预期的那样,前 4 个参数在 x64 中通过寄存器传递。
- 其余参数按与 x86 中相同的顺序放入堆栈。
- 与 x86 相反,在 x64 中我们不使用
push
指令。相反,我们在main
的开头保留足够的堆栈 space 并使用mov
指令将参数添加到堆栈。 - 在 x64 中,在
call
之后没有堆栈清理发生,但是在main
. 结束时
这让我想到了我的问题:
- 为什么 x64 使用
mov
而不是push
?我认为它只是更有效率并且在 x86 中不可用。 - 为什么在 x64 中
call
指令后没有堆栈清理? - Microsoft 选择在 x64 程序集中忽略
__stdcall
的原因是什么? 来自 docs:On ARM and x64 processors, __stdcall is accepted and ignored by the compiler
Here 是示例代码和程序集。
- Why does x64 use
mov
rather thanpush
? I assume it's just more efficient and wasn't available in x86.
不是这个原因。这两条指令也存在于 x86 汇编语言中。
您的编译器没有为 x64 代码发出 push
指令的原因可能是因为它无论如何都必须直接调整堆栈指针,以创建 32 字节的“阴影 space " 用于被调用的函数。有关“影子 space”的更多信息,请参阅 this link(由 @NateEldredge 提供)。
使用 push
指令分配 32 个字节的“shadow space”将需要 4 个 64 位 push
指令,但只需要一个 sub
指令。这就是为什么它更喜欢使用 sub
指令。由于它无论如何都使用 sub
指令来创建 32 字节的影子 space,因此将 sub
指令的操作数从 32 更改为 72 没有任何惩罚,它分配了 72 字节的影子堆栈上的内存,足以在堆栈上传递 3 个参数(其他 4 个在 CPU 寄存器中传递)。
我不明白为什么它在堆栈上分配 72 个字节,但是,根据我的计算,它只需要 56 个字节(32 个字节的“shadow space”和 24 个在堆栈上传递的 3 个参数的字节)。编译器可能会为局部变量或异常处理保留额外的 16 个字节,当编译器优化处于活动状态时,这些字节可能会被优化掉。
- Why is there no stack cleanup after the call instructions in x64?
调用指令后有堆栈清理。这条线是这样的
add rsp, 72
会。
但是,由于某些原因(可能是为了提高性能),x64 编译器仅在调用函数结束时执行清理,而不是在每次函数调用之后执行清理。这意味着对于 x64 编译器,所有函数调用为其参数共享相同的堆栈 space,而对于 x86 编译器,堆栈 space 在每次函数调用时分配和清理。
- What's the reason that Microsoft chose to ignore __stdcall in x64 assembly?
关键字 _stdcall
和 _cdecl
指定 32 位调用约定。这就是它们与 64 位程序(即 x64)无关的原因。在 x64 上,只有 standard calling convention and the extended __vectorcall
调用约定。