Reputation: 75
Just a test for __cdecl
calling convention.
It's a cmake project and only have 1 source file:
#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;
}
I'm using set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /FA")
to output the assembly code.
and when I build with cmake -G "Visual Studio 15"
, It's build a 32bit application, and every thing is in anticipation:
...
; 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
...
You can see the arguments is passed by push 2
and push 1
instructions, it's __cdecl
call convention.
But if I'm using cmake -G "Visual Studio 15 Win64"
to build a 64bit application, the __cdecl
annotation seems not work (arguments not passed by stack):
...
; 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
...
the arguments is passed by register edx
and ecx
, not passed by stack.
So why arguments not passed by stack in x64 even if I specify __cdecl
and what should I do if I want do same things in x64 environments.
Upvotes: 4
Views: 2528
Reputation: 30115
x64 has it's own calling conventions.
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
You can see it using ECX and EDX for the int a
and int b
(as they are 32bits, while the full RCX and RDX is 64bits).
__stdcall
, __fastcall
and __thiscall
are also ignored. __vectorcall
is available (the /Gv switch makes it default) and is another register calling convention, but compared to the x64 default it can use registers in more cases and has some other rule differences.
Upvotes: 7