System V i386 ABI と cdecl 呼び出し規約
Linux と BSD 系の 32 ビットの呼び出し規約で、よく cdecl と呼ばれます。引数はすべてスタックで渡し、結果は EAX、EDX、または x87 スタックで返ります。構造体とアラインメントの細部はシステムによって異なります。
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 構文を切り替えられます。
レジスター
| レジスター | 役割 | 保存される |
|---|---|---|
| EAX | 戻り値。返される構造体のアドレス | いいえ |
| EDX | 64 ビットの戻り値の上位半分 | いいえ |
| ECX | スクラッチ (scratch) | いいえ |
| EBX | 呼び出し先保存。PLT を通した呼び出しでの GOT のアドレス | はい |
| ESI、EDI | 呼び出し先保存 | はい |
| EBP | 呼び出し先保存。関数がフレームポインターを使う場合はフレームポインター | はい |
| ESP | スタックポインター | はい |
| ST0 | float、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
retIntel 構文 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]
retClang 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
retlIntel 構文 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
retIntel 構文 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]
retClang 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
retlIntel 構文 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
retlong 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
retIntel 構文 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]
retClang 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
retlIntel 構文 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値の返し方
| C の型 | 返す場所 |
|---|---|
char、short、int、long、ポインター | EAX。型のサイズを超えるビットは未定義 |
long long | EDX:EAX。上位半分は EDX |
float、double、long double | ST0 (x87 レジスタースタックの先頭) |
_Complex float | EAX (実部) と EDX (虚部) |
__m64、__m128、_Float16 | MM0、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
retIntel 構文 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]
retClang 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
retlIntel 構文 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 はその逆を行います。
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
retIntel 構文 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]
retClang 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
retlIntel 構文 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
retClang 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
retlIntel 構文 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 バイトになります。
| アドレス | 内容 | フレーム |
|---|---|---|
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
retIntel 構文 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]
retClang 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
retlIntel 構文 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
retregparm と Linux カーネル
GCC の regparm(n) 属性は、最初の n 個 (最大 3 個) の整数引数をスタックではなく EAX、EDX、ECX で渡します。可変長引数関数は、それでもすべての引数をスタックで受け取ります。オプション -mregparm=3 はこれをすべての関数に適用します。32 ビットの Linux カーネルは、-freg-struct-return を付け、位置独立コードなしで、そのようにビルドされます。
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:
retIntel 構文 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:
retClang 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:
retlIntel 構文 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:
retClang も regparm をサポートしています。関連する GCC の属性 sseregparm は、最大 3 つの浮動小数点引数を XMM レジスターで渡します。
データ型
i386 では long double は 12 バイトで、double と long long は構造体の中では 4 バイトにしかアラインされません。32 ビットの Windows はそれらを 8 バイトにアラインします。x86 の Android は long double を double と同じにしています。すべてのプラットフォームのサイズは ABI の概要にあります。
出典
- System V Application Binary Interface, Intel386 Architecture Processor Supplement, version 1.2: コミット
20ec676cd56d、2025-08-24 - System V Application Binary Interface, Intel386 Architecture Processor Supplement, Fourth Edition: 2026-09-25 の公開版
- GCC 14.2 manual: x86 Function Attributes: 2026-09-25 の公開版
- Linux 7.3: arch/x86/Makefile: コミット
c03c1a14507d、2026-10-18 - Clang 23.1.2: clang/lib/CodeGen/Targets/X86.cpp: コミット
85ac56026243、2026-09-20 - Wine 11.0: include/msvcrt/corecrt.h: コミット
db11d0fe6a16、2026-01-13