web-dev-qa-db-ja.com

なぜスタックを逆方向に拡張するのですか?

CコードをコンパイルしてAssemblyを見ると、スタックは次のように逆方向に成長します。

__main:
    pushq   %rbp
    movl    $5, -4(%rbp)
     popq    %rbp
    ret
_

-4(%rbp)-これは、ベースポインターまたはスタックポインターがメモリアドレスを上に移動するのではなく、実際に下に移動することを意味しますか?何故ですか?

$5, -4(%rbp)$5, +4(%rbp)に変更し、コードをコンパイルして実行したところ、エラーは発生しませんでした。では、なぜメモリスタックを逆戻りする必要があるのでしょうか。

47
alex

これは、ベースポインターまたはスタックポインターが実際にメモリアドレスを上に移動するのではなく、下に移動していることを意味しますか?何故ですか?

はい、Push命令はスタックポインタをデクリメントしてスタックに書き込みますが、popはその逆を行い、スタックから読み取ってスタックポインタをインクリメントします。

これは、メモリが限られているマシンの場合、スタックが高く配置されて下向きに成長し、ヒープが低く配置されて上向きに成長するという点で、多少歴史的です。 「空きメモリ」のギャップは1つだけあります-ヒープとスタックの間であり、このギャップは共有されます。どちらかが個別に必要に応じてギャップに成長できます。したがって、スタックとヒープが衝突して空きメモリが残っていない場合にのみ、プログラムがメモリ不足になります。

スタックとヒープの両方が同じ方向に成長する場合、2つのギャップがあり、スタックは実際にヒープのギャップに成長できません(その逆も問題です)。

元々、プロセッサには専用のスタック処理命令がありませんでした。ただし、ハードウェアにスタックサポートが追加されたため、このパターンは下降傾向にあり、プロセッサは現在もこのパターンに従います。

64ビットマシンでは、複数のギャップを許容するのに十分なアドレススペースがあると主張することができます。また、証拠として、複数のギャップは、プロセスに複数のスレッドがある場合に必ず当てはまります。これは状況を変えるための十分な動機ではありませんが、複数のギャップシステムでは、成長方向は間違いなく任意であるため、伝統/互換性がスケールを傾けます。


スタックの方向を変更するには、CPUスタックの処理命令を変更するか、専用のプッシュおよびポップ命令の使用をあきらめる必要があります(例:Pushpopcallret、その他)。

MIPS命令セットアーキテクチャには専用のPushpopがないため、どちらの方向にもスタックを拡大することが現実的です。シングルギャップのメモリレイアウトが必要な場合もあります。スレッドプロセスですが、スタックを上向きに、ヒープを下向きに成長させる可能性があります。ただし、これを行った場合、一部のC varargs コードでは、ソースまたは内部のパラメータ渡しで調整が必要になる場合があります。

(実際、MIPSには専用のスタック処理がないため、スタックをポップするために正確な逆を使用している限り、スタックへのプッシュにプリまたはポストインクリメントまたはプリまたはポストデクリメントを使用できます。オペレーティングシステムは、選択されたスタック使用モデルを尊重します。実際、一部の組み込みシステムと一部の教育システムでは、MIPSスタックが上位に拡張されています。)

87
Erik Eidt

特定のシステムでは、スタックは上位メモリアドレスから始まり、下位メモリアドレスに「成長」します。 (低から高への対称的なケースも存在します)

そして、-4と+4から変更して実行したので、それが正しいとは限りません。実行中のプログラムのメモリレイアウトはより複雑であり、この非常に単純なプログラムで即座にクラッシュしなかったという事実に貢献した可能性のある他の多くの要因に依存しています。

8
nadir

スタックポインタは、割り当てられたスタックメモリと割り当てられていないスタックメモリの境界を指します。下向きに成長するということは、割り当てられたスタックスペース内の最初の構造のstartを指し、他の割り当てられた項目がより大きなアドレスに続くことを意味します。割り当てられた構造の開始を指すポインターを持つことは、逆の方法よりもはるかに一般的です。

現在、最近の多くのシステムでは、スタックframes用の別個のレジスターがあり、ローカル変数ストレージが散在している呼び出しチェーンを理解するために、ある程度確実に展開できます。一部のアーキテクチャでこのスタックフレームレジスタが設定される方法は、スタックポインタbeforeではなく、ローカル変数ストレージbehindを指すようになることを意味します。したがって、このスタックフレームレジスタを使用するには、負のインデックスが必要です。

スタックフレームとそのインデックスはコンパイルされたコンピューター言語の側面であるため、貧弱なアセンブリ言語プログラマーではなく、コンパイラーのコードジェネレーターが「不自然さ」に対処する必要があることに注意してください。

したがって、スタックを選択して下方向に拡大する歴史的な理由はありましたが(アセンブリ言語でプログラミングし、適切なスタックフレームを設定しなくても、スタックの一部は保持されます)、見えにくくなっています。

1
user327022