32-bit programs for Windows, built with MSVC, clang-cl or MinGW-w64, share one set of rules for data and registers, but a function may be declared with one of five calling conventions: __cdecl, the default of C and C++; __stdcall, the convention of the Windows API; __fastcall; __thiscall, the default of C++ member functions; and __vectorcall. They are Microsoft’s, but MinGW-w64 GCC and Clang implement them too. System V i386, the 32-bit ABI of Linux, corresponds to __cdecl, with differences in returning structs and in stack alignment. 64-bit Windows has a single convention, Microsoft x64.

The listings on this page come from Clang for i686-pc-windows-msvc, which follows MSVC’s ABI, and from MinGW-w64 GCC, both at -O2. Both are compiled for the Pentium 4 (-march=pentium4), since MSVC generates SSE2 code by default; MinGW-w64 GCC would otherwise target the Pentium Pro, and it still computes with the x87 unit unless told otherwise. The buttons next to each listing switch between AT&T and Intel syntax.

What all conventions share

  • EAX, ECX and EDX are scratch; EBX, ESI, EDI and EBP are callee-saved, as in System V.
  • Results return in EAX, 64-bit values in EDX:EAX, and float and double on the x87 stack, in ST0. Unlike on Linux, a struct of 1, 2, 4 or 8 bytes returns in EAX or EDX:EAX too.
  • Arguments that are not in registers are pushed from right to left, each widened to a multiple of 4 bytes.
  • The stack is aligned to 4 bytes only.

The five conventions

The 32-bit Windows calling conventions. The decorated names are those of a C function f(int, int); the number is the size of the arguments in bytes.
ConventionRegister argumentsWho removes the stack argumentsDecorated nameUsed by
__cdeclnonethe caller_fC and C++ functions by default; all variadic functions
__stdcallnonethe called function_f@8the Windows API (WINAPI, CALLBACK)
__fastcallthe first two 4-byte or smaller integers in ECX and EDXthe called function@f@8code that asks for it
__thiscallthis in ECXthe called functionnone: C++ onlyC++ member functions by default
__vectorcallintegers as __fastcall; floating-point and vector values in XMM0 to XMM5the called functionf@@8vector code

The difference that matters most is who removes the arguments. With __cdecl, the caller adds to ESP after the call; with the others, the function returns with ret n, which pops n bytes of arguments along with the return address. A function called with the wrong convention leaves ESP off by the size of its arguments, and the caller crashes later. The decorated names make that mistake a link error instead: _f@8 does not link with _f.

A variadic function cannot remove arguments it does not know about, so variadic functions are always __cdecl, whatever their declaration says.

One function in three conventions. The __cdecl one returns with a plain ret, the __stdcall one removes its 12 bytes of arguments with ret $12, and the __fastcall one takes a and b in ECX and EDX and removes only c. In call_both, Clang adds 12 to ESP after the __cdecl call but not after the __stdcall one; MinGW-w64 GCC, which stores arguments into space it reserved once, makes up for the callee’s pop with subl $12.

win32-conventions.c

/* The same function in three 32-bit Windows conventions: who removes
   the arguments, which go in registers, and how the name is decorated. */
int __cdecl add_cdecl(int a, int b, int c)
{
	return a + b + c;
}

int __stdcall add_stdcall(int a, int b, int c)
{
	return a + b + c;
}

int __fastcall add_fastcall(int a, int b, int c)
{
	return a + b + c;
}

extern int __cdecl ext_cdecl(int, int, int);
extern int __stdcall ext_stdcall(int, int, int);

int call_both(void)
{
	return ext_cdecl(1, 2, 3) + ext_stdcall(4, 5, 6);
}

Clang 23.1.2 i686-pc-windows-msvc

AT&T syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -S win32-conventions.c

_add_cdecl:
        movl    8(%esp), %eax
        addl    4(%esp), %eax
        addl    12(%esp), %eax
        retl

_add_stdcall@12:
        movl    8(%esp), %eax
        addl    4(%esp), %eax
        addl    12(%esp), %eax
        retl    $12

@add_fastcall@12:
        leal    (%ecx,%edx), %eax
        addl    4(%esp), %eax
        retl    $4

_call_both:
        pushl   %esi
        pushl   $3
        pushl   $2
        pushl   $1
        calll   _ext_cdecl
        addl    $12, %esp
        movl    %eax, %esi
        pushl   $6
        pushl   $5
        pushl   $4
        calll   _ext_stdcall@12
        addl    %esi, %eax
        popl    %esi
        retl

Intel syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -masm=intel -S win32-conventions.c

_add_cdecl:
        mov     eax, dword ptr [esp + 8]
        add     eax, dword ptr [esp + 4]
        add     eax, dword ptr [esp + 12]
        ret

_add_stdcall@12:
        mov     eax, dword ptr [esp + 8]
        add     eax, dword ptr [esp + 4]
        add     eax, dword ptr [esp + 12]
        ret     12

@add_fastcall@12:
        lea     eax, [ecx + edx]
        add     eax, dword ptr [esp + 4]
        ret     4

_call_both:
        push    esi
        push    3
        push    2
        push    1
        call    _ext_cdecl
        add     esp, 12
        mov     esi, eax
        push    6
        push    5
        push    4
        call    _ext_stdcall@12
        add     eax, esi
        pop     esi
        ret

MinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) i686-w64-mingw32

AT&T syntax i686-w64-mingw32-gcc -march=pentium4 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S win32-conventions.c

_add_cdecl:
        movl    8(%esp), %eax
        addl    4(%esp), %eax
        addl    12(%esp), %eax
        ret

_add_stdcall@12:
        movl    8(%esp), %eax
        addl    4(%esp), %eax
        addl    12(%esp), %eax
        ret     $12

@add_fastcall@12:
        leal    (%ecx,%edx), %eax
        addl    4(%esp), %eax
        ret     $4

_call_both:
        pushl   %ebx
        subl    $24, %esp
        movl    $3, 8(%esp)
        movl    $2, 4(%esp)
        movl    $1, (%esp)
        call    _ext_cdecl
        movl    $6, 8(%esp)
        movl    $5, 4(%esp)
        movl    %eax, %ebx
        movl    $4, (%esp)
        call    _ext_stdcall@12
        subl    $12, %esp
        addl    %ebx, %eax
        addl    $24, %esp
        popl    %ebx
        ret

Intel syntax i686-w64-mingw32-gcc -march=pentium4 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S win32-conventions.c

_add_cdecl:
        mov     eax, DWORD PTR [esp+8]
        add     eax, DWORD PTR [esp+4]
        add     eax, DWORD PTR [esp+12]
        ret

_add_stdcall@12:
        mov     eax, DWORD PTR [esp+8]
        add     eax, DWORD PTR [esp+4]
        add     eax, DWORD PTR [esp+12]
        ret     12

@add_fastcall@12:
        lea     eax, [ecx+edx]
        add     eax, DWORD PTR [esp+4]
        ret     4

_call_both:
        push    ebx
        sub     esp, 24
        mov     DWORD PTR [esp+8], 3
        mov     DWORD PTR [esp+4], 2
        mov     DWORD PTR [esp], 1
        call    _ext_cdecl
        mov     DWORD PTR [esp+8], 6
        mov     DWORD PTR [esp+4], 5
        mov     ebx, eax
        mov     DWORD PTR [esp], 4
        call    _ext_stdcall@12
        sub     esp, 12
        add     eax, ebx
        add     esp, 24
        pop     ebx
        ret

The Windows API declares its functions WINAPI, a name for __stdcall, and callbacks that Windows calls, such as a window procedure, CALLBACK, which is __stdcall too. A callback declared with the default __cdecl does not match the function pointer type that Windows expects; forced in with a cast, it fails at run time.

thiscall

C++ member functions use __thiscall unless they are variadic: this goes in ECX, the other arguments on the stack, and the member function removes them. Clang’s MinGW-w64 target uses the same convention for member functions, but mangles names the Itanium way of GCC rather than MSVC’s.

A member function with one argument: this in ECX, k at 4(%esp), removed with ret $4. drop_two loads the object’s address into ECX before the call. Clang for MSVC and for MinGW-w64 differ only in the mangled names.

win32-thiscall.cpp

// A C++ member function: this in ECX, the other arguments on the
// stack, removed by the member function itself.
struct Counter {
	int n;
	int add(int k);
	int sub(int k);
};

int Counter::add(int k)
{
	return n += k;
}

int drop_two(Counter *c)
{
	return c->sub(2);
}

Clang 23.1.2 i686-pc-windows-msvc

AT&T syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -S win32-thiscall.cpp

"?add@Counter@@QAEHH@Z":
        movl    (%ecx), %eax
        addl    4(%esp), %eax
        movl    %eax, (%ecx)
        retl    $4

"?drop_two@@YAHPAUCounter@@@Z":
        movl    4(%esp), %ecx
        pushl   $2
        calll   "?sub@Counter@@QAEHH@Z"
        retl

Intel syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -masm=intel -S win32-thiscall.cpp

"?add@Counter@@QAEHH@Z":
        mov     eax, dword ptr [ecx]
        add     eax, dword ptr [esp + 4]
        mov     dword ptr [ecx], eax
        ret     4

"?drop_two@@YAHPAUCounter@@@Z":
        mov     ecx, dword ptr [esp + 4]
        push    2
        call    "?sub@Counter@@QAEHH@Z"
        ret

Clang 23.1.2 i686-w64-windows-gnu

AT&T syntax clang --target=i686-w64-windows-gnu -march=pentium4 -O2 -fno-asynchronous-unwind-tables -S win32-thiscall.cpp

__ZN7Counter3addEi:
        movl    (%ecx), %eax
        addl    4(%esp), %eax
        movl    %eax, (%ecx)
        retl    $4

__Z8drop_twoP7Counter:
        movl    4(%esp), %ecx
        pushl   $2
        calll   __ZN7Counter3subEi
        retl

Intel syntax clang --target=i686-w64-windows-gnu -march=pentium4 -O2 -fno-asynchronous-unwind-tables -masm=intel -S win32-thiscall.cpp

__ZN7Counter3addEi:
        mov     eax, dword ptr [ecx]
        add     eax, dword ptr [esp + 4]
        mov     dword ptr [ecx], eax
        ret     4

__Z8drop_twoP7Counter:
        mov     ecx, dword ptr [esp + 4]
        push    2
        call    __ZN7Counter3subEi
        ret

vectorcall

__vectorcall passes the first two integer arguments like __fastcall, in ECX and EDX, and the first six floating-point or vector arguments in XMM0 to XMM5, by position among the vector arguments. The function removes its stack arguments, and its name gets @@ and the size of its arguments. double values return in XMM0 rather than ST0.

x and y arrive in XMM0 and XMM1, n in ECX, and the result returns in XMM0. The name is scale@@20: two double values and an int.

win32-vectorcall.c

/* __vectorcall: integers in ECX and EDX as with __fastcall, and
   floating-point and vector values in XMM0 to XMM5. */
double __vectorcall scale(double x, int n, double y)
{
	return x * n + y;
}

Clang 23.1.2 i686-pc-windows-msvc

AT&T syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -S win32-vectorcall.c

scale@@20:
        cvtsi2sd        %ecx, %xmm2
        mulsd   %xmm2, %xmm0
        addsd   %xmm1, %xmm0
        retl

Intel syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -masm=intel -S win32-vectorcall.c

scale@@20:
        cvtsi2sd        xmm2, ecx
        mulsd   xmm0, xmm2
        addsd   xmm0, xmm1
        ret

Returning structs

MSVC returns a struct of 1, 2, 4 or 8 bytes in EAX or EDX:EAX, as the BSDs do, and larger ones through memory: the caller passes the address as a hidden first argument on the stack, and the function returns it in EAX. Unlike on Linux, a __cdecl function leaves that hidden argument for the caller to remove.

make_pair returns its 8-byte struct in EDX:EAX. make_triple writes 12 bytes through the pointer at 4(%esp) and returns with a plain ret.

win32-struct-return.c

/* MSVC returns a struct of 8 bytes in EDX:EAX; a larger one through a
   hidden pointer, which the caller removes afterwards. */
struct pair { int a, b; };
struct triple { int a, b, c; };

struct pair make_pair(int a, int b)
{
	struct pair p = { a, b };
	return p;
}

struct triple make_triple(int x)
{
	struct triple t = { x, x, x };
	return t;
}

Clang 23.1.2 i686-pc-windows-msvc

AT&T syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -S win32-struct-return.c

_make_pair:
        movl    4(%esp), %eax
        movl    8(%esp), %edx
        retl

_make_triple:
        movl    4(%esp), %eax
        movl    8(%esp), %ecx
        movl    %ecx, (%eax)
        movl    %ecx, 4(%eax)
        movl    %ecx, 8(%eax)
        retl

Intel syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -masm=intel -S win32-struct-return.c

_make_pair:
        mov     eax, dword ptr [esp + 4]
        mov     edx, dword ptr [esp + 8]
        ret

_make_triple:
        mov     eax, dword ptr [esp + 4]
        mov     ecx, dword ptr [esp + 8]
        mov     dword ptr [eax], ecx
        mov     dword ptr [eax + 4], ecx
        mov     dword ptr [eax + 8], ecx
        ret

MinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) i686-w64-mingw32

AT&T syntax i686-w64-mingw32-gcc -march=pentium4 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S win32-struct-return.c

_make_pair:
        movl    4(%esp), %eax
        movl    8(%esp), %edx
        ret

_make_triple:
        movd    8(%esp), %xmm0
        movl    4(%esp), %eax
        movdqu  %xmm0, %xmm1
        movd    %xmm0, 8(%eax)
        punpckldq       %xmm0, %xmm1
        movq    %xmm1, (%eax)
        ret

Intel syntax i686-w64-mingw32-gcc -march=pentium4 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S win32-struct-return.c

_make_pair:
        mov     eax, DWORD PTR [esp+4]
        mov     edx, DWORD PTR [esp+8]
        ret

_make_triple:
        movd    xmm0, DWORD PTR [esp+8]
        mov     eax, DWORD PTR [esp+4]
        movdqu  xmm1, xmm0
        movd    DWORD PTR [eax+8], xmm0
        punpckldq       xmm1, xmm0
        movq    QWORD PTR [eax], xmm1
        ret

The stack

32-bit Windows compilers assume only 4-byte alignment on entry to a function, as the original i386 ABI did, where Linux has moved to 16. A function that needs more for its own data, such as a local array aligned for SSE, aligns its frame itself: it saves EBP, rounds ESP down, and addresses its arguments through EBP.

A local array aligned to 16 bytes: both Windows compilers round ESP down with andl $-16 and read the argument x at 8(%ebp). GCC for Linux, which can count on 16-byte alignment on entry, only subtracts a suitable amount and finds the array at 12(%esp).

win32-align.c

/* The stack is only 4-byte aligned on 32-bit Windows: a function that
   needs more for its own data aligns ESP itself. */
extern void use(double *d);

void aligned_local(double x)
{
	double d[2] __attribute__((aligned(16))) = { x, x };
	use(d);
}

Clang 23.1.2 i686-pc-windows-msvc

AT&T syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -S win32-align.c

_aligned_local:
        pushl   %ebp
        movl    %esp, %ebp
        andl    $-16, %esp
        subl    $32, %esp
        movsd   8(%ebp), %xmm0
        movl    %esp, %eax
        movsd   %xmm0, (%esp)
        movsd   %xmm0, 8(%esp)
        pushl   %eax
        calll   _use
        addl    $4, %esp
        movl    %ebp, %esp
        popl    %ebp
        retl

Intel syntax clang --target=i686-pc-windows-msvc -march=pentium4 -O2 -fno-asynchronous-unwind-tables -masm=intel -S win32-align.c

_aligned_local:
        push    ebp
        mov     ebp, esp
        and     esp, -16
        sub     esp, 32
        movsd   xmm0, qword ptr [ebp + 8]
        mov     eax, esp
        movsd   qword ptr [esp], xmm0
        movsd   qword ptr [esp + 8], xmm0
        push    eax
        call    _use
        add     esp, 4
        mov     esp, ebp
        pop     ebp
        ret

MinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) i686-w64-mingw32

AT&T syntax i686-w64-mingw32-gcc -march=pentium4 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S win32-align.c

_aligned_local:
        pushl   %ebp
        movl    %esp, %ebp
        andl    $-16, %esp
        subl    $32, %esp
        movsd   8(%ebp), %xmm0
        leal    16(%esp), %eax
        movl    %eax, (%esp)
        unpcklpd        %xmm0, %xmm0
        movups  %xmm0, 16(%esp)
        call    _use
        leave
        ret

Intel syntax i686-w64-mingw32-gcc -march=pentium4 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S win32-align.c

_aligned_local:
        push    ebp
        mov     ebp, esp
        and     esp, -16
        sub     esp, 32
        movsd   xmm0, QWORD PTR [ebp+8]
        lea     eax, [esp+16]
        mov     DWORD PTR [esp], eax
        unpcklpd        xmm0, xmm0
        movups  XMMWORD PTR [esp+16], xmm0
        call    _use
        leave
        ret

GCC 14.2.0 (Debian 14.2.0-19) i386-linux-gnu

AT&T syntax gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S win32-align.c

aligned_local:
        pushl   %ebx
        call    __x86.get_pc_thunk.bx
        addl    $_GLOBAL_OFFSET_TABLE_, %ebx
        subl    $36, %esp
        fldl    44(%esp)
        leal    12(%esp), %eax
        fstl    12(%esp)
        fstpl   20(%esp)
        pushl   %eax
        call    use@PLT
        addl    $40, %esp
        popl    %ebx
        ret

__x86.get_pc_thunk.bx:
        movl    (%esp), %ebx
        ret

Intel syntax gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S win32-align.c

aligned_local:
        push    ebx
        call    __x86.get_pc_thunk.bx
        add     ebx, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
        sub     esp, 36
        fld     QWORD PTR 44[esp]
        lea     eax, 12[esp]
        fst     QWORD PTR 12[esp]
        fstp    QWORD PTR 20[esp]
        push    eax
        call    use@PLT
        add     esp, 40
        pop     ebx
        ret

__x86.get_pc_thunk.bx:
        mov     ebx, DWORD PTR [esp]
        ret

The ABI overview gives the sizes of the C types on 32-bit Windows: double and long long are aligned to 8 bytes in structs, where Linux aligns them to 4, and long double is the same as double for MSVC but the 12-byte x87 type for MinGW-w64.

Sources

  1. Microsoft Learn: Argument passing and naming conventions: commit f70d88cd5da7, 2026-09-24
  2. Microsoft Learn: __cdecl: commit f70d88cd5da7, 2026-09-24
  3. Microsoft Learn: __stdcall: commit f70d88cd5da7, 2026-09-24
  4. Microsoft Learn: __fastcall: commit f70d88cd5da7, 2026-09-24
  5. Microsoft Learn: __thiscall: commit f70d88cd5da7, 2026-09-24
  6. Microsoft Learn: __vectorcall: commit f70d88cd5da7, 2026-09-24
  7. Microsoft Learn: Decorated names: commit f70d88cd5da7, 2026-09-24
  8. Microsoft Learn: /arch (x86): commit f70d88cd5da7, 2026-09-24
  9. Microsoft Learn: Windows Data Types: commit e103fa4e8810, 2026-09-15
  10. GCC 14.2 manual: x86 Function Attributes: as published on 2026-09-25
  11. Clang 23.1.2: clang/lib/CodeGen/Targets/X86.cpp: commit 85ac56026243, 2026-09-20