C / C ++の関数呼び出しのスタックフレームを理解していますか?


19

スタックフレームがどのように構築され、どの変数(パラメーター)がどの順序でスタックにプッシュされるかを理解しようとしていますか?一部の検索結果は、C / C ++コンパイラが関数内で実行された操作に基づいて決定することを示しました。たとえば、関数が渡されたint値を1だけインクリメントし(++演算子に類似)、それを返すことになっている場合、関数のすべてのパラメーターとローカル変数をレジスターに入れます。

どのレジスタが戻り値または値渡しパラメータに使用されるのか疑問に思っています。参照はどのように返されますか?コンパイラーはeax、ebx、ecx、edxをどのように選択しますか?

関数呼び出し中にレジスター、スタック、およびヒープ参照がどのように使用、構築、および破棄されるかを理解するには、何を知る必要がありますか?


これはかなり読みにくいです(テキストの壁)。あなたは気になり、編集をより良い形にあなたのポストをINGの?
グナット

1
この質問は私にはかなり広いようです。また、これはプラットフォーム固有のものではありませんか?
カザーク


回答:


11

Dirkが言ったことに加えて、スタックフレームの重要な使用法は、レジスタの以前の値を保存して、関数呼び出し後に復元できるようにすることです。そのため、レジスターを使用してパラメーターを渡し、値を返し、戻りアドレスを保存するプロセッサーでも、これらのレジスターの値は関数呼び出しの前にスタックに保存されるため、呼び出し後に復元できます。これにより、1つの関数が独自のパラメーターを上書きしたり、独自のリターンアドレスを忘れたりすることなく、別の関数を呼び出すことができます。

したがって、一般的な「汎用」システムで関数Aから関数Bを呼び出すには、次の手順が必要になる場合があります。

  • 機能A:
    • 戻り値用のスペースをプッシュ
    • プッシュパラメーター
    • 返信先をプッシュ
  • 関数Bにジャンプします
  • 機能B:
    • 前のスタックフレームのアドレスをプッシュする
    • この関数が使用するレジスタの値をプッシュします(したがって、それらを復元できます)
    • ローカル変数のプッシュスペース
    • 必要な計算を行う
    • レジスタを復元する
    • 前のスタックフレームを復元する
    • 関数の結果を保存する
    • 返信先にジャンプ
  • 機能A:
    • パラメータをポップします
    • 戻り値をポップします

これは決して関数呼び出しが機能する唯一の方法ではありません(また、1つまたは2つの順序が狂っている可能性もあります)が、スタックを使用してプロセッサがネストされた関数呼び出しを処理する方法を知る必要があります。


ここで「プッシュ」とはどういう意味ですか?何を作ればいいのか分かりません。
トマーシュザト-復活モニカ

2
@TomášZato pushpopは、スタック上の2つの基本操作です。スタックは、書籍のスタックのような後入れ先出し構造です。するとpush、スタックの一番上に新しいオブジェクトを置きます。あなたはときpopあなたはスタックの先頭からオブジェクトを取っています。中央でオブジェクトを挿入または削除することはできません。スタックの一番上でのみ操作できます。Wikipediaで、スタック全般、特にプログラムスタックについて詳しく読むことができます。
カレブ

11

これは、使用されている呼び出し規約に依存します。呼び出し規約を定義する人はだれでもこの決定を行うことができます。

x86の最も一般的な呼び出し規約では、パラメーターを渡すためにレジスターは使用されません。パラメーターは、右端のパラメーターからスタックにプッシュされます。戻り値はeaxに配置され、余分なスペースが必要な場合はedxを使用できます。参照とポインタは両方ともeaxのアドレスの形式で返されます。


5

スタックを非常によく理解していると、プログラムでメモリがどのように機能するかを理解でき、プログラムでメモリがどのように機能するかを理解できれば、プログラムでの関数ストアの方法を理解でき、プログラムでの関数ストアの方法を理解できれば、再帰関数の機能と再帰関数がどのように機能するかを理解します。コンパイラーがどのように機能するかを理解します。

スタックの仕組みを説明しましょう。

最初に、関数がスタックにどのように格納されるかを知る必要があります。

ヒープストアの動的メモリ割り当て値。スタックストアの自動割り当てと削除の値。

ここに画像の説明を入力してください

例で理解しましょう:

def hello(x):
    if x==1:
        return "op"
    else:
        u=1
        e=12
        s=hello(x-1)
        e+=1
        print(s)
        print(x)
        u+=1
    return e

hello(4)

このプログラムの一部を理解してください:

ここに画像の説明を入力してください

では、スタックとは何か、スタックパーツとは何かを見てみましょう。

ここに画像の説明を入力してください

スタックの割り当て:

すべてのローカル変数をロードしたか、スタックからすぐに戻る何かがスタックフレームに関係なく、関数が「戻る」場合、1つ覚えておいてください。これは、再帰関数がベース条件を取得し、ベース条件の後にリターンを置くと、ベース条件がプログラムの「他の」部分にあるローカル変数をロードするのを待たず、すぐにスタックから現在のフレームを返すことを意味します次のフレームがアクティベーションレコードにあることを返します。これを実際に見てください:

ここに画像の説明を入力してください

ブロックの割り当て解除:

そのため、関数がreturnステートメントを見つけるたびに、スタックから現在のフレームを削除します。

スタック値から戻ると、スタックで割り当てた順序と逆の順序で戻ります。

ここに画像の説明を入力してください

これらは非常に短い説明であり、スタックと二重再帰についてさらに詳しく知りたい場合は、このブログの2つの投稿を読んでください:

段階的なスタックの詳細

スタックを使用した段階的な二重再帰の詳細


3

探しているのは、アプリケーションバイナリインターフェイス -ABI と呼ばれるものです。

ABIを綴る各コンパイラーの仕様があります。

通常、各プラットフォームは、コンパイラ間の相互運用性をサポートするために、ABIを指定します。たとえば、x86呼び出し規約では、x86およびx86-64の典型的な呼び出し規約を詳しく説明しています。ただし、ウィキペディアよりも公式のドキュメントが必要です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.