7

古い質問の繰り返しのように感じたら申し訳ありません。スタック オーバーフロー (tanenbaum による最新のオペレーティング システムの本) に関するいくつかの質問を調べましたが、これに関する疑問を解消する必要があります。

まず、この構造をよりよく理解するために、より詳細に調べるべき本/リソースをいただければ幸いです。これらがOSの本で一般的に説明されている概念なのか、プログラミング言語やアーキテクチャの本なのか、私には理解できません。

質問する前に、スタック/ヒープに関する読み取りに基づいて調査結果をリストします。

ヒープ

  • すべてのインスタンス変数、動的に割り当てられた (new/malloc)、およびグローバル変数のみを含む
  • データ構造ヒープを使用しなくなり、より複雑な構造を使用
  • メモリ ロケーション、割り当てられたメモリを担当する個々のプロセスを介したアクセス
  • 最適化とメモリの割り当ては OS によって行われます (はいまたはいいえの場合は、ヒープ、OS、またはランタイム環境を管理しているのは誰かという私の質問に答えてください)
  • 参照にアクセスできるプロセス内のすべてのスレッド間で共有

スタック

  • すべてのローカル変数のみが含まれます。(関数呼び出しでプッシュ)
  • 操作には実際のスタック データ構造を使用
  • 隣接しているためアクセスが速い

さて、同じことに関する私の質問のいくつかについて。

  1. グローバル変数、どこに割り当てられますか? (私の考えでは、それらはヒープに割り当てられます。もしそうなら、実行時またはコンパイル時にいつ割り当てられますか、そしてもう1つの質問は、このメモリを(削除を使用する場合のように)クリアできますか?)
  2. ヒープの構造は何ですか?ヒープはどのように構成されていますか (OS またはランタイム環境 (C/C++ コンパイラによって設定されている) によって管理されていますか)。
  3. スタックはメソッドとそのローカル変数のみを保持していますか?
  4. 各アプリケーション (プロセス) には個別のヒープが与えられますが、ヒープ割り当てを超えた場合、OS がそれ以上のメモリを割り当てることができなかったということですか? (メモリ不足により、断片化を避けるためにOSが再割り当てされると想定しています)
  5. ヒープは、プロセス内のすべてのスレッドからアクセスできます (これは本当だと思います)。はいの場合、すべてのスレッドがインスタンス変数、動的に割り当てられた変数、グローバル変数にアクセスできます (参照がある場合)
  6. 異なるプロセスは、お互いのヒープにアクセスできません (アドレスが渡されても)
  7. スタック オーバーフローがクラッシュする
    • 現在のスレッドのみ
    • 現在のプロセス
    • すべてのプロセス
  8. C/C++ では、実行時に関数内のブロック変数のスタックにメモリが割り当てられますか (たとえば、コードのサブブロック (たとえば、For ループ) が新しい変数を作成する場合、実行時に割り当てられるのはスタック(またはヒープ)または事前に割り当てられていますか?)いつ削除されますか(ブロックレベルのスコープ、それはどのように維持されますか)。これに関する私の信念は、スタックへのすべての追加は実行時にブロックの開始前に行われ、そのブロックの最後に到達するたびに、その時点までに追加されたすべての要素がプッシュされるということです。
  9. スタック レジスタに対する CPU のサポートは、メモリへの通常のアクセスを介してインクリメント (ポップ) およびデクリメント (プッシュ) できるスタック ポインタに限定されます。(これは本当ですか?)
  10. 最後に、メイン メモリ上に存在する OS/ランタイム環境によって生成されたスタック構造とヒープ構造の両方が (抽象化として?)

私はこれがたくさんあることを知っており、全体を通して非常に混乱しているように見えます.これらの問題を解決するために正しい方向に私を向けていただければ幸いです!

4

2 に答える 2

8
  1. グローバル変数は、コンパイル時に配置されるメモリの静的セクションに割り当てられます。値は、起動時にmain入力される前に初期化されます。もちろん、初期化はヒープに割り当てることができます (つまり、静的に割り当てられstd::stringた構造体自体は静的に配置されたメモリに置かれますが、含まれる文字列データは起動時にヒープに割り当てられます)。これらのものは、通常のプログラムのシャットダウン中に削除されます。その前にそれらを解放することはできません。必要に応じて、値をポインターでラップし、プログラムの起動時にポインターを初期化することができます。

  2. ヒープはアロケータ ライブラリによって管理されます。C ランタイムに付属しているものもありますが、標準のアロケーターの代わりに使用できるtcmallocjemallocなどのカスタムのものもあります。これらのアロケータは、システム コールを使用して OS から大量のメモリページを取得し、malloc を呼び出すとこれらのページの一部を提供します。ヒープの構成はいくぶん複雑で、アロケーターによって異なります。Web サイトでそれらがどのように機能するかを調べることができます。

  3. はいっぽい。ただし、ライブラリ関数をalloca使用して、スタック上にスペースのチャンクを作成し、それを必要に応じて使用できます。

  4. 各プロセスには個別のメモリ空間があります。つまり、すべて単独であり、他のプロセスは存在しないと見なされます。一般的に、OS は要求があれば追加のメモリを提供しますが、( ulimitLinux のように) 制限を強制することもでき、その時点で追加のメモリの提供を拒否できます。断片化は OS にとって問題ではありません。これは、ページ単位でメモリが提供されるためです。ただし、プロセスの断片化により、空きスペースがある場合でも、アロケーターがより多くのページを要求する場合があります。

  5. はい。

  6. はい。ただし、一般に、複数のプロセスがアクセスできる共有メモリ領域を作成する OS 固有の方法があります。

  7. スタック オーバーフロー自体は何もクラッシュしません。他の値を保持している可能性のある場所にメモリ値が書き込まれるため、メモリが破損します。破損したメモリを操作すると、クラッシュが発生します。プロセスがマップされていないメモリ (以下の注を参照) にアクセスすると、スレッドだけでなくプロセス全体がクラッシュします。メモリ空間が分離されているため、他のプロセスには影響しません。(これは、すべてのプロセスが同じメモリ空間を共有する Windows 95 のような古いオペレーティング システムには当てはまりません)。

  8. C++ では、ブロックに入るときにスタック割り当てオブジェクトが作成され、ブロックから出るときに破棄されます。ただし、スタック上の実際のスペースはそれほど正確に割り当てられない場合がありますが、構築と破棄はそれらの特定のポイントで行われます。

  9. x86 プロセスのスタック ポインタは、任意に操作できます。コンパイラでは、一連のプッシュ操作を実行する代わりに、スタック ポインターにスペースを単純に追加し、スタック上の値にメモリを設定するコードを生成するのが一般的です。

  10. プロセスのスタックとヒープはすべて同じメモリ空間に存在します。

メモリがどのように構成されているかの概要が役立つ場合があります。

  • カーネルが認識する物理メモリがあります。
  • プロセスが要求すると、カーネルは物理メモリのページを仮想メモリのページにマップします。
  • プロセスは、システム上の他のプロセスに関係なく、独自の仮想メモリ空​​間で動作します。
  • プロセスが開始されると、実行可能ファイル (コード、グローバルなど) のセクションがこれらの仮想メモリ ページの一部に配置されます。
  • アロケータは、malloc 呼び出しを満たすためにプロセスからページを要求します。このメモリはヒープを構成します。
  • スレッド (またはプロセスの初期スレッド) が開始されると、スタックを形成するいくつかのページを OS に要求します。(ヒープアロケーターに問い合わせて、それが提供するスペースをスタックとして使用することもできます)。
  • プログラムの実行中は、アドレス空間、ヒープ、スタックなどのすべてのメモリに自由にアクセスできます。
  • マップされていないメモリ空間の領域にアクセスしようとすると、プログラムがクラッシュします。(より具体的には、OS からシグナルを受け取り、それを処理することを選択できます)。
  • スタック オーバーフローは、プログラムがそのようなマップされていない領域にアクセスする原因となる傾向があります。これが、スタック オーバーフローによってプログラムがクラッシュする傾向がある理由です。
于 2013-01-24T03:39:48.320 に答える
2
  1. グローバル変数が割り当てられる場所は、実際にはシステムに依存します。それらをバイナリに静的に配置するシステムもあれば、ヒープに割り当てるシステムもあれば、スタックに割り当てるシステムもあります。グローバル変数がポインターの場合、deleteそれが指す値を取得できますが、それ以外の方法でそのメモリをクリアする方法はありません。グローバル変数のデストラクタは、アプリケーションの終了時に自動的に呼び出されます (SIGTERM を使用しない場合もあります)。
  2. 私は肯定的ではありませんが、オペレーティング システム、特にカーネルによって管理されていると思います。
  3. はい、特定のポイントまでのみです。たとえば、値が (しゃれた意図はありませんが) 積み重なるため、無限再帰を行うことはできません。あなたは、それを待って、スタックオーバーフローで終わるでしょう(AHH、そこにあります、彼はそれを言いました!)
  4. 一部のオペレーティング システムでは、別のプロセスによってヒープ サイズの制限が課される場合がありますが、通常、メモリの割り当てに失敗するのは、メモリが残っていないことが原因です。
  5. すべてのスレッドは共通のヒープを共有するため、すべてのスレッドが動的に割り当てられたグローバル変数にアクセスできます。
  6. 一般的には正しいですが、いくつかの本当に必要最小限のアーキテクチャでは、これは正しくない場合があります。ほとんどの場合、OS は仮想テーブルのコンテキストでプロセスを実行するため、実際に使用しているポインター値は、表示されるものとは異なるメモリ アドレスを指します。
  7. プロセスが OS レベルのプロセスを意味する場合は、現在のプロセス。
  8. 私はそれが正しいと仮定していますが、私は自分自身を知りません。
  9. これは私の操舵室の外です。
  10. はい、そうです。前述したように、ほとんどの OS は vtable を使用してプロセス ポインターをメイン メモリにマップします。また、ディスクへのページング (スワッピング) も検討してください。
于 2013-01-24T03:38:25.843 に答える