System V i386 ABI は、System V ABI の Intel386 補足である i386 psABI で定められています。32 ビットの Linux がこれを使い、32 ビット x86 の Android と 32 ビットの BSD 系も使います。システム間には少数の相違点があり、以下の各節で指摘します。32 ビットの Windows は別の規約を使います。それは Windows x86 のページで説明しています。x86-64 と比べると、ほとんどすべてが異なります。汎用レジスターは 8 つで、引数はそれらでは渡されません。

このページのリストは、i386-linux-gnu 向けの GCC と Clang を、どちらも -O2 で i686 プロセッサー向け (-march=i686) に使ったものです。これは Debian の GCC がデフォルトで生成するもので、浮動小数点演算には x87 ユニットが使われます。この指定がなければ Clang は Pentium 4 をターゲットにして SSE2 で計算しますが、それによってコードは変わっても呼び出し規約は変わりません。どちらのコンパイラーも Linux ではデフォルトで位置独立コードを生成するので、__x86.get_pc_thunk.bx の呼び出しがあります (後述)。各リストの横のボタンで AT&T 構文と Intel 構文を切り替えられます。

レジスター

System V i386 ABI のレジスター
レジスター役割保存される
EAX戻り値。返される構造体のアドレスいいえ
EDX64 ビットの戻り値の上位半分いいえ
ECXスクラッチ (scratch)いいえ
EBX呼び出し先保存。PLT を通した呼び出しでの GOT のアドレスはい
ESI、EDI呼び出し先保存はい
EBP呼び出し先保存。関数がフレームポインターを使う場合はフレームポインターはい
ESPスタックポインターはい
ST0float、double、long double 型の戻り値いいえ
ST1 から ST7、MM0 から MM7スクラッチ。呼び出し時に x87 ユニットは x87 モードでなければならないいいえ
XMM0 から XMM2最初の 3 つの __m128 引数。XMM0 は __m128 を返すいいえ
XMM3 から XMM7、K0 から K7スクラッチいいえ
GSシステム用に予約済み。Linux ではスレッドポインターいいえ

x86-64 と同じく、呼び出し時と戻り時には方向フラグがクリアされており、MXCSR の制御ビットと x87 制御ワードは呼び出し先保存です。

x は f の呼び出しを越えて残る必要があるので、ESI に移されます。keep は先に ESI を保存します。EBX も保存されます。呼び出しのための GOT のアドレスを保持するからです。

i386-callee-saved.c

/* EBX, ESI, EDI and EBP belong to the caller. */
extern int f(int);

int keep(int x)
{
	return f(x) + x;
}

GCC 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 i386-callee-saved.c

keep:
        pushl   %esi
        pushl   %ebx
        call    __x86.get_pc_thunk.bx
        addl    $_GLOBAL_OFFSET_TABLE_, %ebx
        subl    $16, %esp
        movl    28(%esp), %esi
        pushl   %esi
        call    f@PLT
        addl    $20, %esp
        addl    %esi, %eax
        popl    %ebx
        popl    %esi
        ret

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

Intel 構文 gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-callee-saved.c

keep:
        push    esi
        push    ebx
        call    __x86.get_pc_thunk.bx
        add     ebx, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
        sub     esp, 16
        mov     esi, DWORD PTR 28[esp]
        push    esi
        call    f@PLT
        add     esp, 20
        add     eax, esi
        pop     ebx
        pop     esi
        ret

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

Clang 23.1.2 i686-linux-gnu

AT&T 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S i386-callee-saved.c

keep:
        pushl   %ebx
        pushl   %esi
        pushl   %eax
        calll   .L0$pb
.L0$pb:
        popl    %ebx
.Ltmp0:
        addl    $_GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L0$pb), %ebx
        movl    16(%esp), %esi
        movl    %esi, (%esp)
        calll   f@PLT
        addl    %esi, %eax
        addl    $4, %esp
        popl    %esi
        popl    %ebx
        retl

Intel 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-callee-saved.c

keep:
        push    ebx
        push    esi
        push    eax
        call    .L0$pb
.L0$pb:
        pop     ebx
.Ltmp0:
        add     ebx, offset _GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L0$pb)
        mov     esi, dword ptr [esp + 16]
        mov     dword ptr [esp], esi
        call    f@PLT
        add     eax, esi
        add     esp, 4
        pop     esi
        pop     ebx
        ret

引数の渡し方

引数はすべてスタックで渡され、右から左へプッシュされるので、最初の引数は戻りアドレスのすぐ上、関数に入った時点で 4(%esp) に来ます。各引数は 4 バイトの倍数を占めます。char や short は 4 バイトのスロット 1 つ全体を使い、long long や double は 2 つ使います。構造体は値渡しでスタックにコピーされ、4 バイトの倍数までパディングされます。例外はベクトル型だけで、最初の 3 つの __m64 引数は MM0 から MM2 に、最初の 3 つの __m128 引数は XMM0 から XMM2 に入ります。

4(%esp)、8(%esp)、12(%esp) にある 3 つの int 引数。call3 はそれらを逆順にプッシュし、呼び出しの後で取り除きます。呼び出し元が引数を取り除くことが、「cdecl」が通常意味するものです。

i386-args.c

/* Every argument on the stack, first argument at the lowest address,
   right above the return address. */
int sum3(int a, int b, int c)
{
	return a + b + c;
}

extern int take3(int, int, int);

int call3(void)
{
	return take3(1, 2, 3) + 1;
}

GCC 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 i386-args.c

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

call3:
        pushl   %ebx
        call    __x86.get_pc_thunk.bx
        addl    $_GLOBAL_OFFSET_TABLE_, %ebx
        subl    $12, %esp
        pushl   $3
        pushl   $2
        pushl   $1
        call    take3@PLT
        addl    $24, %esp
        addl    $1, %eax
        popl    %ebx
        ret

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

Intel 構文 gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-args.c

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

call3:
        push    ebx
        call    __x86.get_pc_thunk.bx
        add     ebx, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
        sub     esp, 12
        push    3
        push    2
        push    1
        call    take3@PLT
        add     esp, 24
        add     eax, 1
        pop     ebx
        ret

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

Clang 23.1.2 i686-linux-gnu

AT&T 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S i386-args.c

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

call3:
        pushl   %ebx
        subl    $8, %esp
        calll   .L1$pb
.L1$pb:
        popl    %ebx
.Ltmp0:
        addl    $_GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L1$pb), %ebx
        subl    $4, %esp
        pushl   $3
        pushl   $2
        pushl   $1
        calll   take3@PLT
        addl    $16, %esp
        incl    %eax
        addl    $8, %esp
        popl    %ebx
        retl

Intel 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-args.c

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

call3:
        push    ebx
        sub     esp, 8
        call    .L1$pb
.L1$pb:
        pop     ebx
.Ltmp0:
        add     ebx, offset _GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L1$pb)
        sub     esp, 4
        push    3
        push    2
        push    1
        call    take3@PLT
        add     esp, 16
        inc     eax
        add     esp, 8
        pop     ebx
        ret
2 つの long long 引数は、それぞれ下位半分を先にして 2 スロットずつ使います。結果は EDX:EAX で返ります。

i386-long-long.c

/* 64-bit values take two stack slots, and return in EDX:EAX. */
long long add64(long long a, long long b)
{
	return a + b;
}

GCC 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 i386-long-long.c

add64:
        movl    12(%esp), %eax
        movl    16(%esp), %edx
        addl    4(%esp), %eax
        adcl    8(%esp), %edx
        ret

Intel 構文 gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-long-long.c

add64:
        mov     eax, DWORD PTR 12[esp]
        mov     edx, DWORD PTR 16[esp]
        add     eax, DWORD PTR 4[esp]
        adc     edx, DWORD PTR 8[esp]
        ret

Clang 23.1.2 i686-linux-gnu

AT&T 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S i386-long-long.c

add64:
        movl    12(%esp), %eax
        addl    4(%esp), %eax
        movl    16(%esp), %edx
        adcl    8(%esp), %edx
        retl

Intel 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-long-long.c

add64:
        mov     eax, dword ptr [esp + 12]
        add     eax, dword ptr [esp + 4]
        mov     edx, dword ptr [esp + 16]
        adc     edx, dword ptr [esp + 8]
        ret

値の返し方

i386 psABI が値を返す場所
C の型返す場所
char、short、int、long、ポインターEAX。型のサイズを超えるビットは未定義
long longEDX:EAX。上位半分は EDX
float、double、long doubleST0 (x87 レジスタースタックの先頭)
_Complex floatEAX (実部) と EDX (虚部)
__m64、__m128、_Float16MM0、XMM0、XMM0
構造体と共用体 (Linux)呼び出し元が用意するメモリ

浮動小数点の結果は x87 のレジスタースタックで返り、呼び出し元はその値を使わない場合でもポップしなければなりません。そうしないとスタックがいっぱいになってしまいます。

half は ST0 で返します。drop は sqr の結果を無視しますが、それでも fstp %st(0) でポップします。

i386-float.c

/* float and double are passed on the stack and return in ST0, which
   the caller must pop even if it ignores the value. */
double half(double x)
{
	return x / 2;
}

extern double sqr(double x);

void drop(double x)
{
	sqr(x);
}

GCC 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 i386-float.c

half:
        call    __x86.get_pc_thunk.ax
        addl    $_GLOBAL_OFFSET_TABLE_, %eax
        flds    .LC0@GOTOFF(%eax)
        fmull   4(%esp)
        ret

drop:
        pushl   %ebx
        call    __x86.get_pc_thunk.bx
        addl    $_GLOBAL_OFFSET_TABLE_, %ebx
        subl    $16, %esp
        pushl   28(%esp)
        pushl   28(%esp)
        call    sqr@PLT
        fstp    %st(0)
        addl    $24, %esp
        popl    %ebx
        ret

.LC0:
        .long   1056964608

__x86.get_pc_thunk.ax:
        movl    (%esp), %eax
        ret

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

Intel 構文 gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-float.c

half:
        call    __x86.get_pc_thunk.ax
        add     eax, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
        fld     DWORD PTR .LC0@GOTOFF[eax]
        fmul    QWORD PTR 4[esp]
        ret

drop:
        push    ebx
        call    __x86.get_pc_thunk.bx
        add     ebx, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
        sub     esp, 16
        push    DWORD PTR 28[esp]
        push    DWORD PTR 28[esp]
        call    sqr@PLT
        fstp    st(0)
        add     esp, 24
        pop     ebx
        ret

.LC0:
        .long   1056964608

__x86.get_pc_thunk.ax:
        mov     eax, DWORD PTR [esp]
        ret

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

Clang 23.1.2 i686-linux-gnu

AT&T 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S i386-float.c

.LCPI0_0:
        .long   0x3f000000

half:
        calll   .L0$pb
.L0$pb:
        popl    %eax
.Ltmp0:
        addl    $_GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L0$pb), %eax
        fldl    4(%esp)
        fmuls   .LCPI0_0@GOTOFF(%eax)
        retl

drop:
        pushl   %ebx
        subl    $8, %esp
        calll   .L1$pb
.L1$pb:
        popl    %ebx
.Ltmp1:
        addl    $_GLOBAL_OFFSET_TABLE_+(.Ltmp1-.L1$pb), %ebx
        fldl    16(%esp)
        fstpl   (%esp)
        calll   sqr@PLT
        fstp    %st(0)
        addl    $8, %esp
        popl    %ebx
        retl

Intel 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-float.c

.LCPI0_0:
        .long   0x3f000000

half:
        call    .L0$pb
.L0$pb:
        pop     eax
.Ltmp0:
        add     eax, offset _GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L0$pb)
        fld     qword ptr [esp + 4]
        fmul    dword ptr [eax + .LCPI0_0@GOTOFF]
        ret

drop:
        push    ebx
        sub     esp, 8
        call    .L1$pb
.L1$pb:
        pop     ebx
.Ltmp1:
        add     ebx, offset _GLOBAL_OFFSET_TABLE_+(.Ltmp1-.L1$pb)
        fld     qword ptr [esp + 16]
        fstp    qword ptr [esp]
        call    sqr@PLT
        fstp    st(0)
        add     esp, 8
        pop     ebx
        ret

構造体の返し方

psABI は、すべての構造体と共用体をメモリを通して返します。呼び出し元は結果のアドレスを最初の隠し引数としてスタックで渡し、呼び出された関数はその引数を ret $4 で自分でポップし、そのアドレスを EAX で返します。System V i386 の関数がスタックから引数を取り除くのは、この場合だけです。

FreeBSD、OpenBSD、DragonFly BSD、および 32 ビットの macOS は、小さな構造体を代わりにレジスターで返します。1、2、4 または 8 バイトの構造体は、EAX、または EAX と EDX で返ります。GCC のオプション -freg-struct-return は同じことを行い、-fpcc-struct-return はその逆を行います。

8 バイトの構造体を返す例。Linux では、make_pair は 4(%esp) のポインターを通して構造体を書き込み、そのポインターを ret $4 でポップします。use_pair はローカル変数のアドレスを渡します。FreeBSD では EAX と EDX で返ります。

i386-struct-return.c

/* Returning a struct: Linux passes a hidden pointer, which the callee
   pops; FreeBSD returns a struct of 8 bytes in EAX and EDX. */
struct pair { int a, b; };

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

extern struct pair get_pair(void);

int use_pair(void)
{
	return get_pair().b;
}

GCC 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 i386-struct-return.c

make_pair:
        movl    4(%esp), %eax
        movl    8(%esp), %edx
        movl    %edx, (%eax)
        movl    12(%esp), %edx
        movl    %edx, 4(%eax)
        ret     $4

use_pair:
        pushl   %ebx
        call    __x86.get_pc_thunk.bx
        addl    $_GLOBAL_OFFSET_TABLE_, %ebx
        subl    $24, %esp
        leal    8(%esp), %eax
        subl    $12, %esp
        pushl   %eax
        call    get_pair@PLT
        movl    24(%esp), %eax
        addl    $36, %esp
        popl    %ebx
        ret

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

Intel 構文 gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-struct-return.c

make_pair:
        mov     eax, DWORD PTR 4[esp]
        mov     edx, DWORD PTR 8[esp]
        mov     DWORD PTR [eax], edx
        mov     edx, DWORD PTR 12[esp]
        mov     DWORD PTR 4[eax], edx
        ret     4

use_pair:
        push    ebx
        call    __x86.get_pc_thunk.bx
        add     ebx, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
        sub     esp, 24
        lea     eax, 8[esp]
        sub     esp, 12
        push    eax
        call    get_pair@PLT
        mov     eax, DWORD PTR 24[esp]
        add     esp, 36
        pop     ebx
        ret

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

Clang 23.1.2 i686-linux-gnu

AT&T 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S i386-struct-return.c

make_pair:
        movl    4(%esp), %eax
        movl    12(%esp), %ecx
        movl    8(%esp), %edx
        movl    %edx, (%eax)
        movl    %ecx, 4(%eax)
        retl    $4

use_pair:
        pushl   %ebx
        subl    $24, %esp
        calll   .L1$pb
.L1$pb:
        popl    %ebx
.Ltmp0:
        addl    $_GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L1$pb), %ebx
        leal    16(%esp), %eax
        movl    %eax, (%esp)
        calll   get_pair@PLT
        subl    $4, %esp
        movl    20(%esp), %eax
        addl    $24, %esp
        popl    %ebx
        retl

Intel 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-struct-return.c

make_pair:
        mov     eax, dword ptr [esp + 4]
        mov     ecx, dword ptr [esp + 12]
        mov     edx, dword ptr [esp + 8]
        mov     dword ptr [eax], edx
        mov     dword ptr [eax + 4], ecx
        ret     4

use_pair:
        push    ebx
        sub     esp, 24
        call    .L1$pb
.L1$pb:
        pop     ebx
.Ltmp0:
        add     ebx, offset _GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L1$pb)
        lea     eax, [esp + 16]
        mov     dword ptr [esp], eax
        call    get_pair@PLT
        sub     esp, 4
        mov     eax, dword ptr [esp + 20]
        add     esp, 24
        pop     ebx
        ret

Clang 23.1.2 i686-unknown-freebsd14

AT&T 構文 clang --target=i686-unknown-freebsd14 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S i386-struct-return.c

make_pair:
        pushl   %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax
        movl    12(%ebp), %edx
        popl    %ebp
        retl

use_pair:
        pushl   %ebp
        movl    %esp, %ebp
        calll   get_pair
        movl    %edx, %eax
        popl    %ebp
        retl

Intel 構文 clang --target=i686-unknown-freebsd14 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-struct-return.c

make_pair:
        push    ebp
        mov     ebp, esp
        mov     eax, dword ptr [ebp + 8]
        mov     edx, dword ptr [ebp + 12]
        pop     ebp
        ret

use_pair:
        push    ebp
        mov     ebp, esp
        call    get_pair
        mov     eax, edx
        pop     ebp
        ret

スタック

スタックは下へ伸び、関数に入った時点で、戻りアドレスのすぐ上のアドレスである ESP + 4 は 16 の倍数です。したがって、別の関数を呼び出す関数は、呼び出し時に ESP を 16 の倍数に保ちます。__m256 または __m512 の引数をスタックで渡す場合、この境界は 32 バイトまたは 64 バイトになります。

i386 のスタックフレームのレイアウト (高いアドレスから低いアドレスへ)
アドレス内容フレーム
8(%ebp) 以上引数。最初のものが最も低いアドレス呼び出し元
4(%ebp)call がプッシュした戻りアドレス呼び出し先
0(%ebp)関数がフレームポインターを使う場合、保存した呼び出し元の EBP呼び出し先
その下保存したレジスターとローカル変数呼び出し先
0(%esp)次の呼び出しの引数。16 バイトにアラインされる呼び出し先

レッドゾーン (red zone) はありません。ESP より下の領域は、どこもシグナルハンドラーから守られていません。

16 バイトのアラインメントは ABI より新しいものです。SCO と AT&T による元の Intel386 補足は 4 バイトのアラインメントしか求めておらず、32 ビット Windows のコンパイラーは今もそれ以上を仮定しません (Windows x86 を参照)。GCC は、スタック上の SSE データをアラインできるよう、ずっと以前からデフォルトで 16 バイトにアラインしており、現在の psABI はそれを規則にしています。Wine が実装する Windows API 関数のように、4 バイトにしかアラインされていないスタックで呼ばれる可能性のある関数には、GCC の force_align_arg_pointer 属性を付けます。この属性は、関数に入るときにスタックをアラインし直します。

リスト 2 では、GCC の call3 に入った時点で ESP は 16 の倍数より 12 バイト上にあります。EBX のプッシュと subl $12 で 16 バイトを使うので位置関係は変わらず、3 つの 4 バイト引数で ESP は呼び出しのための 16 の倍数まで下がります。

EBX と位置独立コード

i386 にはデータに対する命令ポインター相対のアドレス指定がないので、位置独立コードは、call で自分のアドレスを読み込むことでグローバルオフセットテーブル (GOT) を見つけます。__x86.get_pc_thunk.bx は戻りアドレスを返し、コードはそれに GOT のオフセットを加えます。プロシージャリンケージテーブル (PLT) を通した呼び出しは EBX に GOT のアドレスがあることを期待します。そのため、ほかの関数を呼び出す関数は EBX を設定し、EBX は呼び出し先保存なので最初にプッシュされます。ほとんどの Linux ディストリビューションはプログラムを位置独立実行ファイルとしてビルドしているので、このコードはどこにでも現れます。

可変長引数関数

可変長引数関数は、名前のない引数をほかの引数と同様にスタック上、名前付き引数のすぐ上に見つけます。そのため va_list は単純なポインターで、va_arg はそれを引数に沿って 4 バイト以上ずつ進めます。x86-64 の AL のような隠し引数はありません。

va_start は va_list が fmt の次の引数である 20(%esp) を指すようにし、log_msg はそれを vlog に渡します。

i386-varargs.c

/* Variadic arguments are on the stack like all others, so va_list is
   a pointer to the next one. */
#include <stdarg.h>

extern int vlog(const char *fmt, va_list ap);

int log_msg(const char *fmt, ...)
{
	va_list ap;
	int r;

	va_start(ap, fmt);
	r = vlog(fmt, ap);
	va_end(ap);
	return r;
}

GCC 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 i386-varargs.c

log_msg:
        pushl   %ebx
        call    __x86.get_pc_thunk.bx
        addl    $_GLOBAL_OFFSET_TABLE_, %ebx
        subl    $8, %esp
        leal    20(%esp), %eax
        subl    $8, %esp
        pushl   %eax
        pushl   28(%esp)
        call    vlog@PLT
        addl    $24, %esp
        popl    %ebx
        ret

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

Intel 構文 gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-varargs.c

log_msg:
        push    ebx
        call    __x86.get_pc_thunk.bx
        add     ebx, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
        sub     esp, 8
        lea     eax, 20[esp]
        sub     esp, 8
        push    eax
        push    DWORD PTR 28[esp]
        call    vlog@PLT
        add     esp, 24
        pop     ebx
        ret

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

Clang 23.1.2 i686-linux-gnu

AT&T 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S i386-varargs.c

log_msg:
        pushl   %ebx
        subl    $8, %esp
        calll   .L0$pb
.L0$pb:
        popl    %ebx
.Ltmp0:
        addl    $_GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L0$pb), %ebx
        movl    16(%esp), %eax
        leal    20(%esp), %ecx
        movl    %ecx, 4(%esp)
        subl    $8, %esp
        pushl   %ecx
        pushl   %eax
        calll   vlog@PLT
        addl    $24, %esp
        popl    %ebx
        retl

Intel 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S i386-varargs.c

log_msg:
        push    ebx
        sub     esp, 8
        call    .L0$pb
.L0$pb:
        pop     ebx
.Ltmp0:
        add     ebx, offset _GLOBAL_OFFSET_TABLE_+(.Ltmp0-.L0$pb)
        mov     eax, dword ptr [esp + 16]
        lea     ecx, [esp + 20]
        mov     dword ptr [esp + 4], ecx
        sub     esp, 8
        push    ecx
        push    eax
        call    vlog@PLT
        add     esp, 24
        pop     ebx
        ret

regparm と Linux カーネル

GCC の regparm(n) 属性は、最初の n 個 (最大 3 個) の整数引数をスタックではなく EAX、EDX、ECX で渡します。可変長引数関数は、それでもすべての引数をスタックで受け取ります。オプション -mregparm=3 はこれをすべての関数に適用します。32 ビットの Linux カーネルは、-freg-struct-return を付け、位置独立コードなしで、そのようにビルドされます。

カーネルのオプション。引数は EAX、EDX、ECX で渡され、小さな構造体は EAX と EDX で返るので、make_pair は結果がすでに所定の位置にあり、戻るだけです。

i386-regparm.c

/* Compiled like the 32-bit Linux kernel, with -mregparm=3: the first
   three integer arguments in EAX, EDX and ECX. With
   -freg-struct-return, a struct of 8 bytes returns in EAX and EDX. */
int sum3(int a, int b, int c)
{
	return a + b + c;
}

extern int take3(int, int, int);

int call3(void)
{
	return take3(1, 2, 3) + 1;
}

struct pair { int a, b; };

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

GCC 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 -mregparm=3 -freg-struct-return -fno-pic -S i386-regparm.c

sum3:
        addl    %edx, %eax
        addl    %ecx, %eax
        ret

call3:
        subl    $12, %esp
        movl    $3, %ecx
        movl    $2, %edx
        movl    $1, %eax
        call    take3
        addl    $12, %esp
        addl    $1, %eax
        ret

make_pair:
        ret

Intel 構文 gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -mregparm=3 -freg-struct-return -fno-pic -masm=intel -S i386-regparm.c

sum3:
        add     eax, edx
        add     eax, ecx
        ret

call3:
        sub     esp, 12
        mov     ecx, 3
        mov     edx, 2
        mov     eax, 1
        call    take3
        add     esp, 12
        add     eax, 1
        ret

make_pair:
        ret

Clang 23.1.2 i686-linux-gnu

AT&T 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -mregparm=3 -freg-struct-return -fno-pic -S i386-regparm.c

sum3:
        addl    %edx, %eax
        addl    %ecx, %eax
        retl

call3:
        subl    $12, %esp
        movl    $1, %eax
        movl    $2, %edx
        movl    $3, %ecx
        calll   take3
        incl    %eax
        addl    $12, %esp
        retl

make_pair:
        retl

Intel 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -mregparm=3 -freg-struct-return -fno-pic -masm=intel -S i386-regparm.c

sum3:
        add     eax, edx
        add     eax, ecx
        ret

call3:
        sub     esp, 12
        mov     eax, 1
        mov     edx, 2
        mov     ecx, 3
        call    take3
        inc     eax
        add     esp, 12
        ret

make_pair:
        ret

Clang も regparm をサポートしています。関連する GCC の属性 sseregparm は、最大 3 つの浮動小数点引数を XMM レジスターで渡します。

データ型

i386 では long double は 12 バイトで、double と long long は構造体の中では 4 バイトにしかアラインされません。32 ビットの Windows はそれらを 8 バイトにアラインします。x86 の Android は long double を double と同じにしています。すべてのプラットフォームのサイズは ABI の概要にあります。

出典

  1. System V Application Binary Interface, Intel386 Architecture Processor Supplement, version 1.2: コミット 20ec676cd56d、2025-08-24
  2. System V Application Binary Interface, Intel386 Architecture Processor Supplement, Fourth Edition: 2026-09-25 の公開版
  3. GCC 14.2 manual: x86 Function Attributes: 2026-09-25 の公開版
  4. Linux 7.3: arch/x86/Makefile: コミット c03c1a14507d、2026-10-18
  5. Clang 23.1.2: clang/lib/CodeGen/Targets/X86.cpp: コミット 85ac56026243、2026-09-20
  6. Wine 11.0: include/msvcrt/corecrt.h: コミット db11d0fe6a16、2026-01-13