web-dev-qa-db-ja.com

スタックとヒープ-動的割り当ての質問

ソースは通常、動的に作成された変数はヒープ上に割り当てられ、関数の変数はスタック上にあると述べています。また、スタック上のものは自動的に存在しなくなります。変数を含む関数は終了します。私が正しく理解していれば、クラスのデータフィールドはヒープ上にあります。わかりません、「動的」とはどういう意味ですか?ご覧のように、コードの実行中は、関数変数であっても、変数が内部にあるオブジェクトであっても、作成されるものはすべて動的に動的に作成されます。簡単な説明をいただければ幸いです。ありがとう

6
John V

「動的」は言葉の選択としては不十分だと私は同意しますが、今はそれで行き詰まっています。これは基本的に、関数のローカル変数のようにプログラミング言語によって自動的にではなく、newまたはmallocのようなものを使用して、プログラマーがメモリ割り当てを明示的に行うことを意味します。

4
Karl Bielefeldt

ほとんどのコンテキストでは、「動的」とは、プログラマーが明示的に割り当てるメモリを指します。これは、関数を入力する通常の部分として割り当てられるメモリとは対照的です(パラメーター、ローカル変数などのスペースを予約します)。

例として、次のC関数を見てみましょう。

void foo(int x, int y)
{
  int z;
  char *str = NULL;
  ...
  str = malloc(sizeof *str * y);
  ...
  free(str);
}

関数を入力すると、xyz、およびstrポインター用にスタック上のスペースが自動的に予約されます(スタックエレメントは後入れ先出しの順序で割り当てられることに注意してください)。さらに、各オブジェクト用に予約するスペースのamountは、コンパイル時に固定されます(それぞれのタイプのサイズに基づいて)。スタックフレームを見ると、次のようになります(intchar *はすべて同じサイズであると想定しています)。


+--------+
|        | <-- y           STACK BOTTOM
+--------+
|        | <-- x
+--------+
|        | <-- z
+--------+
|        | <--str          STACK TOP
+--------+

関数を終了すると、 "STACK BOTTOM"と "STACK TOP"の間のすべての要素がポップ(割り当て解除)されます。

このレベルではメモリ割り当てを制御しません。これは、関数に出入りするときにすべて自動的に行われます。ただし、関数内でstrが指すメモリを割り当てたいと思います。さらに、割り当てるメモリの量は固定されていませんが、yパラメーターの値から実行時に決定されます。 malloc呼び出しは、ヒープ(スタックとは異なるメモリ領域であり、管理方法も異なる)から一部のメモリを予約し、そのメモリのアドレスをstrに割り当てます。そのメモリは、freeを呼び出して明示的に解放するまで割り当てられたままです。関数が終了する前にそうしないと、strvariableの割り当てが解除されます(つまり、動的に割り当てられたブロックへの参照が失われます)。それが指すメモリにはありません(これはメモリリークと呼ばれます。ダイナミックブロックへの参照を失うと、freeできなくなります)。

Javaの動作はこれまでとは少し異なります。

public void foo(int x, int y)
{
  int z;
  String str = new String(); // don't need to specify a length as with malloc
  ...
}

Cと同様に、Javaは、xyz、およびstrポインターのそれぞれに対してスタック上のスペースを予約します(はい、Javaは、フード、それはプログラマにポインタ操作を公開しないだけです。Cと同様に、new演算子は実行時にヒープ上のStringオブジェクト用にメモリを予約します。Cとは異なり、Javaはオブジェクトを監視しますヒープが(str変数などの)誰かによって参照されているかどうかを確認します。Java関数が終了すると、動的メモリへのすべての参照が存在しなくなり、自動ガベージコレクタが最終的にそのメモリの割り当てを解除するので、明示的に解放する必要はありません(実際、Javaはfree関数と同等のものを提供しません)。

2
John Bode

スタックは最適化、つまり 実装の詳細 と考える必要があります。残念ながら、メモリ管理である抽象化は漏れやすい傾向があります。リークの多い言語(私はCを見ています)とリークの少ない言語(Javaなど)があります。

メモリが必要な場合、デフォルトでは、すべてがヒープに入ると想像できます。どのような状況でも、ヒープメモリを使用してすべての問題を解決することができます。ヒープへの割り当ては、必要なときに適切なサイズになるまで不明なサイズになる可能性があり、不確定な時間だけ存続する可能性があります。ただし、これらの品質を許可するにはコストがかかります。言語によって管理方法は異なりますが、ヒープ内のメモリの割り当て、割り当て解除、および場合によっては断片化の解消の両方を行うために、言語によって時間と労力が費やされるという点が残っています。

必要なすべてのメモリがそのレベルの柔軟性を必要とするわけではありません。必要なメモリがコンパイル時に既知のサイズであり、特定のウェルを含むスコープを持つコンピュータープログラミングには、非常に一般的なコンテキストがいくつかあります。定義されたプロパティ。メソッドを呼び出すと、パラメーターリスト、戻り値、ローカル変数を表すメモリ内の情報が固定サイズであることを知っています。そして、メモリのすべての新しい割り当ては、割り当てられたときに格納されたメモリの前にスコープを離れることを知っています。これは、スタックと呼ばれるデータ構造の正確な実装に従います。

スタックは、一般的なデータ構造として、後入先出先です。その上にアイテムをプッシュし、アイテムからアイテムをポップします。ポップされたアイテムは、最後にプッシュされたアイテムです(まだ構造内にあります)。したがって、メモリを保持するスタックがあると仮定します。新しいメソッドの実行を開始すると、そのメソッドに割り当てるメモリがわかっています(すべてのメソッドは、引数、戻り値、およびローカル変数用のメモリを必要とします)。そのメモリのすべてをスタックにプッシュする場合、スタック内の「その上」にあるアイテムをプッシュオフする前に、スタックからプッシュオフする必要があります。これは大丈夫です。 「us」を呼び出したメソッドのメモリは、終了するまで解放する必要がないことがわかっています。また、呼び出すメソッドには、解放する前にメモリを割り当てて解放する必要があります。

このタイプのメモリの管理は、ヒープメモリの管理よりもはるかに簡単です。それはveryスタックに新しいメモリをすばやく簡単に追加でき、それを簡単に削除できます。 「スタック」とは、実際には、スタックの「終了」へのポインタを持つメモリの長い連続したブロックのことです。新しいメモリの割り当ては、スタックポインタを増やして、アクセスを許可するアドレスを誰かに伝えるのと同じくらい簡単で、割り当て解除は、スタックポインタを固定値だけ減らすのと同じくらい簡単です。固定値をポインターに加算/減算することは、コンピューターにとってvery高速で簡単です。

したがって、スタックの目的は、追加の機能を許可することではありません。実際、スタックを使用する方がヒープを使用するよりも制限が多く、ほとんどの複雑なプログラムでは、スタックメモリの制限が法外になる状況が少なくとも発生する傾向があるため、依然としてヒープが必要です。スタックメモリは、スタックの制限を満たすメモリの特定の用途の割り当てと割り当て解除を非常に迅速に行うだけです。

0
Servy