Cの関数に引数を渡すことがどのように機能するのか知りたいです。値はどこに保存され、どのように取得されますか?可変引数の受け渡しはどのように機能しますか?また、関連しているので、戻り値はどうですか?
私はCPUレジスタとアセンブラの基本的な知識を持っていますが、GCCが私に吐き出すASMを完全に理解するには十分ではありません。いくつかの簡単な注釈付きの例をいただければ幸いです。
Cの関数に引数を渡すことがどのように機能するのか知りたいです。値はどこに保存され、どのように取得されますか?可変引数の受け渡しはどのように機能しますか?また、関連しているので、戻り値はどうですか?
私はCPUレジスタとアセンブラの基本的な知識を持っていますが、GCCが私に吐き出すASMを完全に理解するには十分ではありません。いくつかの簡単な注釈付きの例をいただければ幸いです。
このコードを検討する:
int foo (int a, int b) {
return a + b;
}
int main (void) {
foo(3, 5);
return 0;
}
でコンパイルするとgcc foo.c -S
、アセンブリ出力が得られます。
foo:
pushl %ebp
movl %esp, %ebp
movl 12(%ebp), %eax
movl 8(%ebp), %edx
leal (%edx,%eax), %eax
popl %ebp
ret
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $5, 4(%esp)
movl $3, (%esp)
call foo
movl $0, %eax
leave
ret
したがって、基本的に、呼び出し元(この場合main
)は、最初にスタックに8バイトを割り当てて、2つの引数に対応し、次に2つの引数を対応するオフセット(4
および0
)でスタックに配置します。次にcall
、制御をに転送する命令が発行されます。foo
ルーティーン。ルーチンはfoo
、スタック内の対応するオフセットから引数を読み取り、それを復元しeax
、呼び出し元が使用できるように戻り値をレジスターに入れます。
これはプラットフォーム固有であり、「ABI」の一部です。実際、一部のコンパイラでは、さまざまな規則から選択することさえできます。
たとえば、Microsoft の Visual Studio は、レジスタを使用する __fastcall 呼び出し規約を提供しています。他のプラットフォームまたは呼び出し規約は、スタックを排他的に使用します。
可変引数は非常によく似た方法で機能します。レジスタまたはスタックを介して渡されます。レジスターの場合、通常、タイプに基づいて昇順になっています。(int a, int b, float c, int d) のようなものがある場合、PowerPC ABI はa
r3、b
r4、d
r5、およびc
fp1 に入れる可能性があります (float レジスターの開始位置を忘れましたが、アイデアはわかります)。 .
戻り値も同じように機能します。
残念ながら、私は多くの例を持っていません。私のアセンブリのほとんどは PowerPC にあります。アセンブリに表示されるのは、r3、r4、r5 を直接処理し、r3 にも戻り値を配置するコードだけです。
あなたの質問は、誰もが SO の投稿で合理的に答えようとする以上のものであり、実装も定義されていることは言うまでもありません。
ただし、x86 の回答に興味がある場合は、この Stanford CS107 Lecture というタイトルのProgramming Paradigmsを視聴することをお勧めします。ここでは、最初の 6 ~ 8 回の講義で、提起した質問に対するすべての回答が非常に詳細に (そして非常に雄弁に) 説明されます。 .
基本的に、C は引数をスタックにプッシュして渡します。ポインター型の場合、ポインターはスタックにプッシュされます。
C についての 1 つのことは、呼び出された関数ではなく、呼び出し元がスタックを復元することです。このように、引数の数は変化する可能性があり、呼び出される関数は渡される引数の数を事前に知る必要はありません。
戻り値は、AX レジスタまたはそのバリエーションで返されます。