関数呼び出しがプログラムによって行われた場合、呼び出された関数は呼び出し元に戻る方法を知っている必要があることを読みました。
私の質問は次のとおりです。呼び出された関数は、呼び出し元に戻る方法をどのように知っていますか? コンパイラを介して舞台裏で機能するメカニズムはありますか?
関数呼び出しがプログラムによって行われた場合、呼び出された関数は呼び出し元に戻る方法を知っている必要があることを読みました。
私の質問は次のとおりです。呼び出された関数は、呼び出し元に戻る方法をどのように知っていますか? コンパイラを介して舞台裏で機能するメカニズムはありますか?
コンパイラは、対象の ABI の一部として定義された特定の「呼び出し規約」に従います。その呼び出し規則には、システムがどのアドレスに戻るべきかを知る方法が含まれます。呼び出し規約は、通常、プロシージャ呼び出しに対するハードウェアのサポートを利用します。たとえば、Intel では、戻りアドレスがスタックにプッシュされます。
...プロセッサーは、
EIP
レジスターの値 (命令に続く命令のオフセットを含むCALL
) をスタックにプッシュします (後で戻り命令ポインターとして使用するため)。
関数から戻るには、次のret
命令を使用します。
... プロセッサは、リターン命令ポインタ (オフセット) をスタックの一番上から
EIP
レジスタにポップし、新しい命令ポインタでプログラムの実行を開始します。
対照的に、ARM では、リターン アドレスはリンク レジスタに置かれます。
BL
およびBLX
命令は、次の命令のアドレスを ( 、リンク レジスタ) にコピーしlr
ますr14
。
通常、リターンはmovs pc, lr
、アドレスをリンク レジスタからプログラム カウンタ レジスタにコピーして戻すことによって行われます。
参考文献:
呼び出し先と呼び出し元の協力が必要です。
呼び出し元は、呼び出し先が返すべきアドレスを (通常はスタックにプッシュするか、レジスタに渡すことによって) 呼び出し先に与えることに同意し、呼び出し先は実行が終了したときにそのアドレスに戻ることに同意します。
これはスタックによって可能になります (特に Intel のようなシステムでは)。たとえば、ローカルに保持caller
する を含むメソッドがあるとします。int
caller(
そのintを呼び出すときはtarget(
、保存する必要があります。これは、呼び出し元のアドレスとともにスタックに置かれます。target(
ロジックを実行し、独自のローカル変数を作成し、他のメソッドを呼び出すことができます。そのローカル変数は、呼び出しのアドレスとともにスタックに配置されます。
終了するtarget(
と、スタックは「展開」されます。target(
のローカル変数を含むスタックの一番上が削除されます。
メソッドが再帰しすぎると、スタックが大きくなりすぎて、「スタック オーバーフロー」が発生する場合があります。