関数のスタックフレームには、レジスターに収まらないものが入ります。スタックで渡す引数、戻りアドレス、関数が使う呼び出し先保存レジスター、ローカル変数です。ABI は、スタック引数の位置や呼び出し時のスタックポインターのアラインメントなど、呼び出し元と呼び出し先が合意しなければならない部分を定め、残りはコンパイラーが配置します。

このページの図は、コンパイルしたコードから描いています。各行は、関数のプロローグの後のスタックポインターまたはフレームポインターを基準としたアドレスを示し、サイトのビルドは、すべてのアドレスを図の元になったリストと照合します。

フレームの各部分

フレームには、上位アドレスから下位アドレスに向かって次のものがあります。

  • スタック引数 (呼び出し元のフレームに属し、戻りアドレスの上にある)
  • call がプッシュした戻りアドレス
  • 関数がフレームポインターを保持する場合は、呼び出し元のフレームポインター
  • 関数が使う呼び出し先保存レジスター
  • ローカル変数と、レジスターをスピル (spill) するスロット
  • 関数が行う呼び出しに渡す引数 (最下部にあり、呼び出された関数がそこで見つける)

2 つの部分は一方の ABI にだけあります。Microsoft x64 にはシャドウスペース (shadow space) があります。これは各呼び出し元が自分のフレームの最下部に確保する 32 バイトで、呼び出された関数はそこに 4 つのレジスター引数を格納できます。System V x86-64 にはレッドゾーン (red zone) があります。これはスタックポインターの下の 128 バイトで、関数はスタックポインターを動かさずにそこを使えます。

フレームポインターがある場合

フレームポインターを保持する関数は、呼び出し元の RBP をプッシュし、RBP がそれを指すようにします。そのため、RSP がどう動いても、関数自身の引数は RBP から正のオフセットに、ローカル変数は負のオフセットにあります。デバッガーやプロファイラーは、保存された RBP の値をたどってフレームからフレームへ移動できます。Linux 向けの GCC と Clang がこれを -O2 で行うのは、-fno-omit-frame-pointer で指示された場合か、実行時にフレームのサイズが変わる場合だけです。

8 つの引数、ローカル配列、2 つの呼び出しを持つ関数を、GCC でフレームポインターありとなしでコンパイルしたもの

frame.c

/* A function with a local array, stack arguments and two calls: every
   part of a stack frame at once. */
extern void use(long long *p, long long n);
extern long long other(long long x);

long long frame(long long a, long long b, long long c, long long d,
		long long e, long long f, long long g, long long h)
{
	long long buf[4] = { a, b, c, g };

	use(buf, h);
	return other(d + e + f) + buf[3];
}

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

AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -fno-omit-frame-pointer -S frame.c

frame:
        pushq   %rbp
        movq    %rsp, %rbp
        pushq   %r13
        movq    %r8, %r13
        pushq   %r12
        movq    %r9, %r12
        pushq   %rbx
        movq    %rcx, %rbx
        subq    $40, %rsp
        movq    %rsi, -56(%rbp)
        movq    16(%rbp), %rax
        movq    24(%rbp), %rsi
        movq    %rdi, -64(%rbp)
        leaq    -64(%rbp), %rdi
        movq    %rdx, -48(%rbp)
        movq    %rax, -40(%rbp)
        call    use@PLT
        leaq    (%rbx,%r13), %rdi
        addq    %r12, %rdi
        call    other@PLT
        addq    -40(%rbp), %rax
        addq    $40, %rsp
        popq    %rbx
        popq    %r12
        popq    %r13
        popq    %rbp
        ret

Intel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -fno-omit-frame-pointer -masm=intel -S frame.c

frame:
        push    rbp
        mov     rbp, rsp
        push    r13
        mov     r13, r8
        push    r12
        mov     r12, r9
        push    rbx
        mov     rbx, rcx
        sub     rsp, 40
        mov     QWORD PTR -56[rbp], rsi
        mov     rax, QWORD PTR 16[rbp]
        mov     rsi, QWORD PTR 24[rbp]
        mov     QWORD PTR -64[rbp], rdi
        lea     rdi, -64[rbp]
        mov     QWORD PTR -48[rbp], rdx
        mov     QWORD PTR -40[rbp], rax
        call    use@PLT
        lea     rdi, [rbx+r13]
        add     rdi, r12
        call    other@PLT
        add     rax, QWORD PTR -40[rbp]
        add     rsp, 40
        pop     rbx
        pop     r12
        pop     r13
        pop     rbp
        ret

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

AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S frame.c

frame:
        pushq   %r12
        movq    %r8, %r12
        pushq   %rbp
        movq    %r9, %rbp
        pushq   %rbx
        movq    %rcx, %rbx
        subq    $32, %rsp
        movq    64(%rsp), %rax
        movq    %rsi, 8(%rsp)
        movq    72(%rsp), %rsi
        movq    %rdi, (%rsp)
        movq    %rsp, %rdi
        movq    %rdx, 16(%rsp)
        movq    %rax, 24(%rsp)
        call    use@PLT
        leaq    (%rbx,%r12), %rdi
        addq    %rbp, %rdi
        call    other@PLT
        addq    24(%rsp), %rax
        addq    $32, %rsp
        popq    %rbx
        popq    %rbp
        popq    %r12
        ret

Intel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S frame.c

frame:
        push    r12
        mov     r12, r8
        push    rbp
        mov     rbp, r9
        push    rbx
        mov     rbx, rcx
        sub     rsp, 32
        mov     rax, QWORD PTR 64[rsp]
        mov     QWORD PTR 8[rsp], rsi
        mov     rsi, QWORD PTR 72[rsp]
        mov     QWORD PTR [rsp], rdi
        mov     rdi, rsp
        mov     QWORD PTR 16[rsp], rdx
        mov     QWORD PTR 24[rsp], rax
        call    use@PLT
        lea     rdi, [rbx+r12]
        add     rdi, rbp
        call    other@PLT
        add     rax, QWORD PTR 24[rsp]
        add     rsp, 32
        pop     rbx
        pop     rbp
        pop     r12
        ret
System V x86-64 で、フレームポインターがある場合の frame のフレーム。引数は RBP の上、ローカル変数は下にあります。
アドレス内容レジスター
RBP+24h, the 8th argument 8 バイト
RBP+16g, the 7th argument 8 バイト
RBP+8return address 8 バイト← RSP on entry
RBP+0the caller’s RBP 8 バイト← RBP
RBP-8R13, R12 and RBX, callee-saved 24 バイト
RBP-40buf[3] 8 バイト
RBP-64buf[0] to buf[2] 24 バイト
RBP-72padding that keeps RSP aligned to 16 8 バイト← RSP
below RSPthe red zone, unused: frame makes calls 128 バイト

GCC 14.2.0 (Debian 14.2.0-19) が x86_64-linux-gnu 向けにコンパイルした frame から描いたもので、ビルド時にすべてのアドレスをそのリストと照合しています。

フレームは戻りアドレスから RSP までの 80 バイトで、16 の倍数です。入口で RSP は 16 の倍数から 8 ずれており、4 回のプッシュと subq $40 で 72 が加わります。これにより、2 つの呼び出しで RSP が 16 にアラインされた状態に保たれます。

フレームポインターがない場合

フレームポインターがない場合、コードはすべてを RSP を基準にアドレス指定し、RBP は呼び出し先保存レジスターの 1 つにすぎなくなります。ここでは GCC は、呼び出しをまたいで生き残る値に RBP を使っています。フレームは小さくなりますが、そのレイアウトはコードからしかわかりません。アンワインドテーブルが存在するのはそのためです。

フレームポインターがない場合の同じフレーム。すべてのオフセットは RSP を基準とし、7 番目の引数は 16(%rbp) ではなく 64(%rsp) にあります。
アドレス内容レジスター
RSP+72h, the 8th argument 8 バイト
RSP+64g, the 7th argument 8 バイト
RSP+56return address 8 バイト
RSP+32R12, RBP and RBX, callee-saved 24 バイト
RSP+24buf[3] 8 バイト
RSP+0buf[0] to buf[2] 24 バイト← RSP

GCC 14.2.0 (Debian 14.2.0-19) が x86_64-linux-gnu 向けにコンパイルした frame から描いたもので、ビルド時にすべてのアドレスをそのリストと照合しています。

Microsoft x64

Microsoft x64 では、4 つのレジスター引数のホームが戻りアドレスの上、つまり呼び出し元が確保したシャドウスペースにあり、スタック引数はその後、入口で 40(%rsp) から始まります。同じ関数は、自分が呼び出す関数のために、自分のフレームの最下部にも独自のシャドウスペースを確保します。

同じ関数を Clang で Microsoft x64 ABI 向けにコンパイルしたもの

frame.c

/* A function with a local array, stack arguments and two calls: every
   part of a stack frame at once. */
extern void use(long long *p, long long n);
extern long long other(long long x);

long long frame(long long a, long long b, long long c, long long d,
		long long e, long long f, long long g, long long h)
{
	long long buf[4] = { a, b, c, g };

	use(buf, h);
	return other(d + e + f) + buf[3];
}

Clang 23.1.2 x86_64-pc-windows-msvc

AT&T 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S frame.c

frame:
        pushq   %rsi
        subq    $64, %rsp
        movq    %r9, %rsi
        movq    136(%rsp), %rax
        movq    128(%rsp), %r9
        movq    %rcx, 32(%rsp)
        movq    %rdx, 40(%rsp)
        movq    %r8, 48(%rsp)
        movq    %r9, 56(%rsp)
        leaq    32(%rsp), %rcx
        movq    %rax, %rdx
        callq   use
        addq    112(%rsp), %rsi
        addq    120(%rsp), %rsi
        movq    %rsi, %rcx
        callq   other
        addq    56(%rsp), %rax
        addq    $64, %rsp
        popq    %rsi
        retq

Intel 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S frame.c

frame:
        push    rsi
        sub     rsp, 64
        mov     rsi, r9
        mov     rax, qword ptr [rsp + 136]
        mov     r9, qword ptr [rsp + 128]
        mov     qword ptr [rsp + 32], rcx
        mov     qword ptr [rsp + 40], rdx
        mov     qword ptr [rsp + 48], r8
        mov     qword ptr [rsp + 56], r9
        lea     rcx, [rsp + 32]
        mov     rdx, rax
        call    use
        add     rsi, qword ptr [rsp + 112]
        add     rsi, qword ptr [rsp + 120]
        mov     rcx, rsi
        call    other
        add     rax, qword ptr [rsp + 56]
        add     rsp, 64
        pop     rsi
        ret
Microsoft x64 での frame のフレーム。戻りアドレスの上に呼び出し元のシャドウスペースとスタック引数、最下部に関数が呼び出しのために確保する独自のシャドウスペースがあります。
アドレス内容レジスター
RSP+136h, the 8th argument 8 バイト
RSP+128g, the 7th argument 8 バイト
RSP+120f, the 6th argument 8 バイト
RSP+112e, the 5th argument 8 バイト
RSP+80the homes of RCX, RDX, R8 and R9: shadow space the caller reserved, unused here 32 バイト
RSP+72return address 8 バイト
RSP+64RSI, callee-saved 8 バイト
RSP+56buf[3] 8 バイト
RSP+32buf[0] to buf[2] 24 バイト
RSP+0shadow space for the calls to use and other 32 バイト← RSP

Clang 23.1.2 が x86_64-pc-windows-msvc 向けにコンパイルした frame から描いたもので、ビルド時にすべてのアドレスをそのリストと照合しています。

Microsoft x64 では、プロローグとエピローグの外で RSP が 16 にアラインされたままである必要があり、レッドゾーンは認められていません。RSP より下には安全な場所がないため、フレームは使う前に確保されます。

可変長引数関数

可変長引数関数は引数を 1 つずつ順にたどれなければなりませんが、引数の一部はレジスターで届いています。64 ビットの 2 つの ABI はどちらもこれをフレームで解決しますが、その方法は異なります。

System V は引数レジスターを 176 バイトのレジスター保存領域に保存し、va_list は va_arg がその中のどこまで進んだかと、スタック引数がどこから続くかを記録します。リストは System V x86-64 のページにあります。

System V x86-64 (GCC) での可変長引数関数 log_msg のフレーム。レジスター保存領域と、その中とスタック引数を指す va_list。
アドレス内容レジスター
RSP+224arguments passed on the stack, if any: overflow_arg_area 8 バイト
RSP+216return address 8 バイト
RSP+208padding 8 バイト
RSP+80XMM0 to XMM7, saved only if AL is not 0 128 バイト
RSP+40RSI, RDX, RCX, R8 and R9 40 バイト
RSP+32the slot of RDI, which holds the named argument fmt and is not saved 8 バイト
RSP+8the va_list: gp_offset 8, fp_offset 48, then the two pointers 24 バイト
RSP+0padding 8 バイト← RSP

GCC 14.2.0 (Debian 14.2.0-19) が x86_64-linux-gnu 向けにコンパイルした log_msg から描いたもので、ビルド時にすべてのアドレスをそのリストと照合しています。

Microsoft x64 には保存領域が要りません。関数は RDX、R8、R9 をスタック引数のすぐ下にあるそれぞれのホームに格納し、すべての引数が 1 つの配列を形作り、単なるポインターである va_list がそれをたどります。リストは Microsoft x64 のページにあります。

Microsoft x64 (Clang) での同じ log_msg のフレーム。レジスター引数のホームが引数配列の先頭になります。
アドレス内容レジスター
RSP+80the 5th argument and those after it, if any 8 バイト
RSP+72the home of R9, the 4th argument 8 バイト
RSP+64the home of R8, the 3rd argument 8 バイト
RSP+56the home of RDX, the 2nd argument: where va_list starts 8 バイト
RSP+48the home of RCX, fmt, not stored 8 バイト
RSP+40return address 8 バイト
RSP+32the va_list, a char * 8 バイト
RSP+0shadow space for the call to vlog 32 バイト← RSP

Clang 23.1.2 が x86_64-pc-windows-msvc 向けにコンパイルした log_msg から描いたもので、ビルド時にすべてのアドレスをそのリストと照合しています。

32 ビット x86 では、すべての引数がすでにスタックにあるため、可変長引数関数には追加のものは何も要りません。

大きなフレームとスタックプローブ

スレッドのスタックはまだマップされていないメモリに向かって伸び、その下にあるガードページまたはギャップが、伸びすぎたスタックを捕らえます。1 ページより大きいフレームは、一度にガードを飛び越えてほかのメモリに着地しうるため、コンパイラーは、関数がスタックを確保するときにページごとに順番にアクセスするようにできます。

Windows では、これは ABI の一部です。1 ページ (4096 バイト) 以上のフレームは、スタックポインターを動かす前に、RAX にサイズを入れて __chkstk を呼び出さなければなりません。x64 版はページをプローブして戻り、その後関数が RAX を減算します。一方、32 ビット版は ESP を自分で動かします。MinGW-w64 は独自の ___chkstk_ms を同じ方法で呼び出します。

Linux では、カーネルがメインスレッドのスタックの下に 256 ページのギャップをほかのマッピングのない状態で確保しており、GCC と Clang は -fstack-clash-protection で求められたときだけスタックをプローブします。その場合 GCC は 1 ページずつ確保し、それぞれに orq $0 で書き込みます。

16 KB のフレーム。Linux 向けの GCC は、1 つの subq で、または -fstack-clash-protection ではページごとに確保します。Windows x64 向けの Clang と MinGW-w64 は、RAX にサイズを入れてプローブを呼び出し、その後サイズを減算します。32 ビットの __chkstk は ESP を自分で動かすため、32 ビットのコードは何も減算しません。

big-frame.c

/* A frame larger than a page: the stack must be touched page by page,
   in order, so that it grows into the guard page rather than past it. */
extern void fill(char *buf);

void big(void)
{
	char buf[16384];

	fill(buf);
}

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

AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S big-frame.c

big:
        subq    $16392, %rsp
        movq    %rsp, %rdi
        call    fill@PLT
        addq    $16392, %rsp
        ret

Intel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S big-frame.c

big:
        sub     rsp, 16392
        mov     rdi, rsp
        call    fill@PLT
        add     rsp, 16392
        ret

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

AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -fstack-clash-protection -S big-frame.c

big:
        leaq    -16384(%rsp), %r11
.LPSRL0:
        subq    $4096, %rsp
        orq     $0, (%rsp)
        cmpq    %r11, %rsp
        jne     .LPSRL0
        subq    $8, %rsp
        movq    %rsp, %rdi
        call    fill@PLT
        addq    $16392, %rsp
        ret

Intel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -fstack-clash-protection -masm=intel -S big-frame.c

big:
        lea     r11, -16384[rsp]
.LPSRL0:
        sub     rsp, 4096
        or      DWORD PTR [rsp], 0
        cmp     rsp, r11
        jne     .LPSRL0
        sub     rsp, 8
        mov     rdi, rsp
        call    fill@PLT
        add     rsp, 16392
        ret

Clang 23.1.2 x86_64-pc-windows-msvc

AT&T 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -S big-frame.c

big:
        movl    $16424, %eax
        callq   __chkstk
        subq    %rax, %rsp
        leaq    40(%rsp), %rcx
        callq   fill
        addq    $16424, %rsp
        retq

Intel 構文 clang --target=x86_64-pc-windows-msvc -O2 -fno-asynchronous-unwind-tables -masm=intel -S big-frame.c

big:
        mov     eax, 16424
        call    __chkstk
        sub     rsp, rax
        lea     rcx, [rsp + 40]
        call    fill
        add     rsp, 16424
        ret

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

AT&T 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -S big-frame.c

big:
        pushq   %rbp
        movl    $16416, %eax
        movq    %rsp, %rbp
        andq    $-16, %rsp
        call    ___chkstk_ms
        subq    %rax, %rsp
        leaq    32(%rsp), %rcx
        call    fill
        leave
        ret

Intel 構文 x86_64-w64-mingw32-gcc -O2 -fno-asynchronous-unwind-tables -masm=intel -S big-frame.c

big:
        push    rbp
        mov     eax, 16416
        mov     rbp, rsp
        and     rsp, -16
        call    ___chkstk_ms
        sub     rsp, rax
        lea     rcx, 32[rsp]
        call    fill
        leave
        ret

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 big-frame.c

_big:
        movl    $16384, %eax
        calll   __chkstk
        movl    %esp, %eax
        pushl   %eax
        calll   _fill
        addl    $16388, %esp
        retl

Intel 構文 clang --target=i686-pc-windows-msvc -march=pentium4 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S big-frame.c

_big:
        mov     eax, 16384
        call    __chkstk
        mov     eax, esp
        push    eax
        call    _fill
        add     esp, 16388
        ret

まとめ

4 つの ABI のスタックフレーム
System V x86-64Microsoft x64System V i386Windows x86
呼び出し時の RSP または ESP のアラインメント1616164
入口でのスタック引数の開始位置RSP+8RSP+40ESP+4ESP+4
呼び出し元が確保する領域なし32 バイトのシャドウスペースなしなし
スタックポインターより下の領域128 バイトのレッドゾーンなしなしなし
大きなフレーム-fstack-clash-protection の場合のみプローブ4096 バイトから __chkstk-fstack-clash-protection の場合のみプローブ4096 バイトから __chkstk

呼び出し規約の比較では、フレームに何が入るか、つまりどの引数がスタックにあり、どのレジスターを保存しなければならないかを説明しています。

出典

  1. System V Application Binary Interface, AMD64 Architecture Processor Supplement, version 1.0: コミット e1ce098331da、2025-03-12
  2. System V Application Binary Interface, Intel386 Architecture Processor Supplement, version 1.2: コミット 20ec676cd56d、2025-08-24
  3. Microsoft Learn: x64 stack usage: コミット f70d88cd5da7、2026-09-24
  4. Microsoft Learn: x64 prolog and epilog: コミット f70d88cd5da7、2026-09-24
  5. GCC 14.2 manual: Program Instrumentation Options: 2026-09-25 の公開版
  6. Linux 7.3: Documentation/admin-guide/kernel-parameters.txt: コミット c03c1a14507d、2026-10-18