32 ビット Windows の呼び出し規約 (cdecl、stdcall、fastcall)
32 ビット Windows は、複数の呼び出し規約が並行して使われる唯一のプラットフォームです。規約によって、どの引数をレジスターで渡すか、残りの引数を誰がスタックから取り除くか、名前をどう装飾するかが異なり、取り違えると実行時にクラッシュします。
MSVC、clang-cl、MinGW-w64 でビルドされた Windows 向けの 32 ビットプログラムは、データとレジスターについて 1 組の規則を共有していますが、関数は 5 つの呼び出し規約のどれかで宣言できます。C と C++ のデフォルトである __cdecl、Windows API の規約である __stdcall、__fastcall、C++ のメンバー関数のデフォルトである __thiscall、そして __vectorcall です。これらは Microsoft の規約ですが、MinGW-w64 GCC と Clang も実装しています。Linux の 32 ビット ABI である System V i386 は __cdecl に相当しますが、構造体の返し方とスタックのアラインメントが異なります。64 ビット Windows の規約は Microsoft x64 の 1 つだけです。
このページのリストは、MSVC の ABI に従う i686-pc-windows-msvc 向けの Clang と MinGW-w64 GCC を、どちらも -O2 で使ったものです。MSVC はデフォルトで SSE2 のコードを生成するので、どちらも Pentium 4 向け (-march=pentium4) にコンパイルしています。この指定がなければ MinGW-w64 GCC は Pentium Pro をターゲットにします。また Pentium 4 向けでも、別に指定しない限り x87 ユニットで計算します。各リストの横のボタンで AT&T 構文と Intel 構文を切り替えられます。
すべての規約に共通すること
- EAX、ECX、EDX はスクラッチ (scratch) で、EBX、ESI、EDI、EBP は System V と同じく呼び出し先保存です。
- 結果は EAX で、64 ビットの値は EDX:EAX で、
floatとdoubleは x87 スタックの ST0 で返ります。Linux と異なり、1、2、4 または 8 バイトの構造体も EAX または EDX:EAX で返ります。 - レジスターに入らない引数は、それぞれ 4 バイトの倍数に広げられ、右から左へプッシュされます。
- スタックは 4 バイトにしかアラインされません。
5 つの規約
f(int, int) のもので、数値は引数のバイト単位のサイズです。| 規約 | レジスター引数 | スタック引数を取り除く側 | 装飾名 | 採用先 |
|---|---|---|---|---|
__cdecl | なし | 呼び出し元 | _f | デフォルトの C と C++ の関数、すべての可変長引数関数 |
__stdcall | なし | 呼び出された関数 | _f@8 | Windows API (WINAPI、CALLBACK) |
__fastcall | 4 バイト以下の最初の 2 つの整数を ECX と EDX で | 呼び出された関数 | @f@8 | これを指定したコード |
__thiscall | ECX で this | 呼び出された関数 | なし (C++ のみ) | デフォルトの C++ メンバー関数 |
__vectorcall | 整数は __fastcall と同じ。浮動小数点値とベクトル値は XMM0 から XMM5 | 呼び出された関数 | f@@8 | ベクトルコード |
最も重要な違いは、誰が引数を取り除くかです。__cdecl では呼び出し元が呼び出しの後で ESP に加算します。ほかの規約では、関数は ret n で戻り、戻りアドレスとともに n バイトの引数をポップします。誤った規約で呼び出された関数は ESP を引数のサイズ分ずらしたままにし、呼び出し元は後でクラッシュします。装飾名のおかげで、この誤りは代わりにリンクエラーになります。_f@8 は _f とはリンクされません。
可変長引数関数は自分が知らない引数を取り除くことができないので、宣言が何であっても常に __cdecl です。
__cdecl 版は単純な ret で戻り、__stdcall 版は 12 バイトの引数を ret $12 で取り除き、__fastcall 版は a と b を ECX と EDX で受け取って c だけを取り除きます。call_both では、Clang は __cdecl の呼び出しの後では ESP に 12 を加えますが、__stdcall の呼び出しの後では加えません。一度だけ確保した領域に引数を格納する MinGW-w64 GCC は、呼び出し先のポップを 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 構文 clang --target=i686-pc-windows-msvc -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
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
retlIntel 構文 clang --target=i686-pc-windows-msvc -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 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
retMinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) i686-w64-mingw32
AT&T 構文 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
retIntel 構文 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
retWindows API は、自身の関数を __stdcall の別名である WINAPI で宣言し、ウィンドウプロシージャのように Windows が呼び出すコールバックを CALLBACK で宣言します。これも __stdcall です。デフォルトの __cdecl で宣言されたコールバックは、Windows が期待する関数ポインター型と一致しません。キャストで無理に渡すと、実行時に失敗します。
thiscall
C++ のメンバー関数は、可変長引数でない限り __thiscall を使います。this は ECX で、ほかの引数はスタックで渡され、メンバー関数がそれらを取り除きます。Clang の MinGW-w64 ターゲットもメンバー関数に同じ規約を使いますが、名前は MSVC の方式ではなく、GCC の Itanium 方式で修飾します。
this は ECX に、k は 4(%esp) にあり、ret $4 で取り除かれます。drop_two は呼び出しの前にオブジェクトのアドレスを ECX に読み込みます。MSVC 向けと MinGW-w64 向けの Clang の違いは、修飾された名前だけです。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 構文 clang --target=i686-pc-windows-msvc -march=pentium4 -mtune=generic -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"
retlIntel 構文 clang --target=i686-pc-windows-msvc -march=pentium4 -mtune=generic -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"
retClang 23.1.2 i686-w64-windows-gnu
AT&T 構文 clang --target=i686-w64-windows-gnu -march=pentium4 -mtune=generic -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
retlIntel 構文 clang --target=i686-w64-windows-gnu -march=pentium4 -mtune=generic -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
retvectorcall
__vectorcall は、最初の 2 つの整数引数を __fastcall と同じく ECX と EDX で渡し、最初の 6 つの浮動小数点引数またはベクトル引数を、ベクトル引数の中での位置に従って XMM0 から XMM5 で渡します。関数は自分のスタック引数を取り除き、その名前には @@ と引数のサイズが付きます。double の値は ST0 ではなく XMM0 で返ります。
x と y は XMM0 と XMM1 で、n は ECX で渡され、結果は XMM0 で返ります。名前は scale@@20 です。2 つの double 値と 1 つの 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 構文 clang --target=i686-pc-windows-msvc -march=pentium4 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S win32-vectorcall.c
scale@@20:
cvtsi2sd %ecx, %xmm2
mulsd %xmm2, %xmm0
addsd %xmm1, %xmm0
retlIntel 構文 clang --target=i686-pc-windows-msvc -march=pentium4 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S win32-vectorcall.c
scale@@20:
cvtsi2sd xmm2, ecx
mulsd xmm0, xmm2
addsd xmm0, xmm1
ret構造体の返し方
MSVC は、BSD 系と同じく 1、2、4 または 8 バイトの構造体を EAX または EDX:EAX で返し、それより大きい構造体はメモリを通して返します。呼び出し元はアドレスを最初の隠し引数としてスタックで渡し、関数はそれを EAX で返します。Linux と異なり、__cdecl 関数はその隠し引数の除去を呼び出し元に任せます。
make_pair は 8 バイトの構造体を EDX:EAX で返します。make_triple は 4(%esp) のポインターを通して 12 バイトを書き込み、単純な 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 構文 clang --target=i686-pc-windows-msvc -march=pentium4 -mtune=generic -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)
retlIntel 構文 clang --target=i686-pc-windows-msvc -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:
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
retMinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) i686-w64-mingw32
AT&T 構文 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)
retIntel 構文 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スタック
32 ビット Windows のコンパイラーは、元の i386 ABI と同じく、関数に入った時点で 4 バイトのアラインメントしか仮定しません。Linux は 16 バイトに移行しています。SSE のためにアラインされたローカル配列のように、自分のデータにより大きなアラインメントを必要とする関数は、フレームを自分でアラインします。EBP を保存し、ESP を切り下げ、引数には EBP を通してアクセスします。
andl $-16 で ESP を切り下げ、引数 x を 8(%ebp) から読みます。関数に入った時点で 16 バイトのアラインメントを当てにできる Linux 向けの GCC は、適切な量を引くだけで、配列を 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 構文 clang --target=i686-pc-windows-msvc -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
movsd %xmm0, (%esp)
movsd %xmm0, 8(%esp)
movl %esp, %eax
pushl %eax
calll _use
addl $4, %esp
movl %ebp, %esp
popl %ebp
retlIntel 構文 clang --target=i686-pc-windows-msvc -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]
movsd qword ptr [esp], xmm0
movsd qword ptr [esp + 8], xmm0
mov eax, esp
push eax
call _use
add esp, 4
mov esp, ebp
pop ebp
retMinGW-w64 GCC 14.2.0 (Debian 14.2.0-19+27+b1) i686-w64-mingw32
AT&T 構文 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
retIntel 構文 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
retGCC 14.2.0 (Debian 14.2.0-19) i386-linux-gnu
AT&T 構文 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
retIntel 構文 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]
ret32 ビット Windows での C の型のサイズは ABI の概要にあります。double と long long は、Linux が 4 バイトにアラインするのに対し、構造体の中で 8 バイトにアラインされます。long double は、MSVC では double と同じですが、MinGW-w64 では 12 バイトの x87 の型です。
出典
- Microsoft Learn: Argument passing and naming conventions: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: __cdecl: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: __stdcall: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: __fastcall: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: __thiscall: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: __vectorcall: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: Decorated names: コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: /arch (x86): コミット
f70d88cd5da7、2026-09-24 - Microsoft Learn: Windows Data Types: コミット
e103fa4e8810、2026-09-15 - GCC 14.2 manual: x86 Function Attributes: 2026-09-25 の公開版
- Clang 23.1.2: clang/lib/CodeGen/Targets/X86.cpp: コミット
85ac56026243、2026-09-20