Calling conventions in Windows on x86

[Moved an old post from 2006 to my new blog]

It is 2 AM in the night and i don’t feel like sleeping so i thought why not i start my blog and here i am with my first blog entry ever.

People who do programming on Windows in C/C++, might wonder sometime, what is the __cdecl or __stdcall in front of a function declaration? These compiler specific prefixes are basically a way to tell the compiler, how to push the function arguments on the stack and how to pop them off the stack. These prefix defines the contract between Caller (the one who calls a function) and Callee (the called function) for argument passing. This contact is known as Calling convention. Usually we should need only one calling convention for argument passing but Windows compilers provide more than one convention because of historical and performance reasons. The three calling conventions available on windows are:

  1. __cdecl
  2. __stdcall
  3. __fastcall

__fastcall and __stdcall are more efficient than __cdecl but they don’t support variable argument functions which is required for functions like printf.

To understand these calling conventions in details, lets take a look at the sample program:

int
__cdecl testCDecl(int a, int b, int c, int d) 
{ 
   return a; 
} 

int 
__stdcall testStdcall(int a, int b, int c, int d) 
{ 
   return a; 
} 

int 
__fastcall testFastcall(int a, int b, int c, int d) 
{ 
   return a; 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
   testCDecl(1, 2, 3, 4); 
   testStdcall(1, 2, 3, 4); 
   testFastcall(1, 2, 3, 4); 

   return 0; 
}

__cdecl is the original C calling convention. In this convention Caller pushes the function arguments on the stack from right to left. Because this convention allows the caller to push variable number of arguments on the stack (for functions like printf), Callee doesn’t know how many bytes it needs to take off from the stack. Hence it is the responsibility of caller to pop these arguments off the stack.

__stdcall (standard calling convention) or also known as PASCAL convention is only supports fixed number of parameters in a function call. In this convention parameters are pushed from right to left but Callee removes them off the stack. This calling convention is the standard calling convention for Win32 APIs.

__fastcall is slightly optimized version of __stdcall convention. It passes first two parameters in ecx and edx respectively but rest of the arguments are pushed on the stack from right to left (similar to other conventions).

Below i will show the disassembly of the code shown above to describe how the parameters are pushed and popped off the stack for the above discussed calling conventions.

; Disassembly of _tmain 
; int _tmain(int argc, _TCHAR* argv[]) 
; { 
push        ebp  
mov         ebp,esp 

; testCDecl(1, 2, 3, 4); 
push        4                   ; <- Push arguments right to left 
push        3                   ; <- on the stack for __cdecl funciton 
push        2                   ; <- Each argument takes 4 bytes on x86 
push        1    
call        testCDecl (401630h) 
add         esp,10h             ; <- testCDecl is called, now pop off the 
                                ; <- arguments off the stack by adding 16 
                                ; <- or 10h from esp (or stack pointer) 

; testStdcall(1, 2, 3, 4); 
push        4                   ; <- Push arguments right to left 
push        3                   ; <- on the stack for __stdcall funciton 
push        2                   ; <- Each argument takes 4 bytes on x86 
push        1    
call        testStdcall (401640h) 
                                ; <- Notice here that after calling testStdcall 
                                ; <- we don't remove the arguments from the 
                                ; <- stack because in __stdcall functions the 
                                ; <- are removed by the Callee and not caller 

; testFastcall(1, 2, 3, 4); 
push        4    
push        3    
mov         edx,2               ; <- Notice here that first and second arguments 
mov         ecx,1               ; <- are passed in ecx and edx respectively 
call        testFastcall (401650h) 
                                ; <- same as __stdcall, callee pops the arguments 
                                ; <- off the stack 

; return 0; 
xor         eax,eax 

; }
pop         ebp  
ret 



; Disassembly of testCDecl 
; int 
; __cdecl testCDecl(int a, int b, int c, int d) 
; { 
push        ebp  
mov         ebp,esp 

; return a; 
mov         eax,dword ptr [a] 

; } 
pop         ebp  
ret      


; Disassembly of testStdCall
; int 
; __stdcall testStdcall(int a, int b, int c, int d) 
; { 
push        ebp  
mov         ebp,esp 

; return a; 
mov         eax,dword ptr [a] 

; } 
pop         ebp  
ret         10h ; <- return to the return address and remove 
                ; <- 16 bytes (or 4 arguments a, b, c and d) 
                ; <- from the stack 


; Disassembly of testFastcall 
; int 
; __fastcall testFastcall(int a, int b, int c, int d) 
; { 
push        ebp  
mov         ebp,esp 
sub         esp,8 
mov         dword ptr [ebp-8],0CCCCCCCCh 
mov         dword ptr [ebp-4],0CCCCCCCCh 
mov         dword ptr [ebp-8],edx 
mov         dword ptr [ebp-4],ecx

; return a; 
mov         eax,dword ptr [a] 

; } 
mov         esp,ebp 
pop         ebp  
ret         8   ; <- return to the return address and remove 
                ; <- 8 bytes  or 2 arguments c and d from the 
                ; <- stack (since a and b were passed in 
                ; <- registers)

For C++ member functions “this” pointer is treated as the first argument and is passed in the ecx register. Other parameters are passed as per the calling convention rules described above.

For x86-64 based systems, there is unified the calling convention but that on some other sleepless night.


This posting is provided “AS IS” with no warranties and confers no rights.

Share