__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 2
和push 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
...
参数通过寄存器 edx
和 ecx
传递,而不是通过堆栈传递。
那么,即使我指定了 __cdecl
,为什么在 x64 中参数没有通过堆栈传递?如果我想在 x64 环境中做同样的事情,我应该怎么做。
x64 有自己的调用约定。
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 a
和 int b
(因为它们是 32 位,而完整的 RCX 和 RDX 是 64 位)。
__stdcall
, __fastcall
and __thiscall
are also ignored. __vectorcall
可用(/Gv 开关使其成为默认值)并且是另一个寄存器调用约定,但与 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 2
和push 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
...
参数通过寄存器 edx
和 ecx
传递,而不是通过堆栈传递。
那么,即使我指定了 __cdecl
,为什么在 x64 中参数没有通过堆栈传递?如果我想在 x64 环境中做同样的事情,我应该怎么做。
x64 有自己的调用约定。
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 a
和 int b
(因为它们是 32 位,而完整的 RCX 和 RDX 是 64 位)。
__stdcall
, __fastcall
and __thiscall
are also ignored. __vectorcall
可用(/Gv 开关使其成为默认值)并且是另一个寄存器调用约定,但与 x64 默认值相比它可以在更多情况下使用寄存器并且有一些其他规则差异。