Delphi でのメモリ割り当てについて

コンピューターのハード ドライブを持っている手

ゲッティイメージズ/ダニエル・サンブラウス





から関数「DoStackOverflow」を 1 回呼び出します。 あなたのコード そして、あなたは EStackOverflow 「スタック オーバーフロー」というメッセージで Delphi によって発生したエラー。

|_+_||_+_|
|_+_|
|_+_|
|_+_|

この「スタック」とは何ですか?また、上記のコードを使用してオーバーフローが発生するのはなぜですか?



したがって、DoStackOverflow 関数は、「終了戦略」なしで再帰的に自分自身を呼び出しています。スピンし続け、決して終了しません。

手っ取り早い解決策は、明らかなバグを取り除き、ある時点で関数が存在することを確認することです (これにより、関数を呼び出した場所からコードを実行し続けることができます)。



先に進み、バグ/例外が解決されたため、気にせずに振り返ることはありません。

それでも、次のような疑問が残ります。 このスタックは何ですか、なぜオーバーフローがあるのですか ?

Delphi アプリケーションのメモリ

Delphi でプログラミングを始めると、上記のようなバグが発生することがあります。それを解決して次に進みます。これはメモリ割り当てに関連しています。ほとんどの場合、メモリの割り当てについて気にする必要はありません。 作成したものを解放する .

Delphi でより多くの経験を積むにつれて、独自のクラスを作成し、それらをインスタンス化し、メモリ管理などに関心を持ち始めます。



ヘルプで、次のようなものを読むところまで到達します。 'ローカル変数 (プロシージャおよび関数内で宣言される) は、アプリケーションの スタック .' そしてまた クラスは参照型であるため、代入時にコピーされず、参照によって渡され、クラスに割り当てられます。 ヒープ .

では、「スタック」とは何ですか、「ヒープ」とは何ですか?



スタックとヒープ

Windows でアプリケーションを実行する の場合、アプリケーションがデータを格納するメモリには、グローバル メモリ、ヒープ、スタックの 3 つの領域があります。

グローバル変数 (その値/データ) は、グローバル メモリに格納されます。グローバル変数用のメモリは、プログラムの開始時にアプリケーションによって予約され、プログラムが終了するまで割り当てられたままになります。グローバル変数用のメモリは「データ セグメント」と呼ばれます。



グローバル メモリは、プログラムの終了時に一度だけ割り当ておよび解放されるため、この記事では気にしません。

スタックとヒープは、動的メモリ割り当てが行われる場所です。関数の変数を作成するとき、関数にパラメーターを送信してその結果値を使用/渡すときにクラスのインスタンスを作成するときです。



スタックとは

関数内で変数を宣言すると、変数を保持するために必要なメモリがスタックから割り当てられます。単純に「var x: integer」と記述し、関数で「x」を使用します。関数が終了すると、メモリの割り当てや解放は気にしません。変数がスコープ外になる (コードが関数を終了する) と、スタック上で取得されたメモリが解放されます。

スタック メモリは、LIFO (「後入れ先出し」) アプローチを使用して動的に割り当てられます。

Delphi プログラム 、スタックメモリはによって使用されます

  • ローカル ルーチン (メソッド、プロシージャ、関数) 変数。
  • ルーチン パラメーターと戻り値の型。
  • Windows API 関数呼び出します。
  • レコード (これが、レコード タイプのインスタンスを明示的に作成する必要がない理由です)。

たとえば、ローカル変数を関数に宣言すると、メモリが自動的に割り当てられるため、スタック上のメモリを明示的に解放する必要はありません。関数が終了すると(Delphi コンパイラの最適化のために、場合によってはそれ以前でも)、変数のメモリが自動的に解放されます。

スタックメモリサイズデフォルトでは、Delphi プログラムに十分な大きさです (非常に複雑です)。プロジェクトのリンカー オプションの [最大スタック サイズ] と [最小スタック サイズ] の値は、既定値を指定します。99.99% であれば、これを変更する必要はありません。

スタックは、メモリ ブロックの山と考えてください。ローカル変数を宣言/使用すると、Delphi メモリ マネージャはブロックを一番上から選択して使用し、不要になるとスタックに戻します。

ローカル変数メモリがスタックから使用されるため、ローカル変数は宣言時に初期化されません。一部の関数で変数「var x: integer」を宣言し、関数に入ったときに値を読み取ってみてください。x には「奇妙な」ゼロ以外の値が含まれます。そのため、値を読み取る前に、必ずローカル変数を初期化 (または値を設定) してください。

LIFO により、スタック (メモリ割り当て) 操作は、スタックを管理するために必要な操作 (プッシュ、ポップ) が少ないため高速です。

ヒープとは

ヒープとは、動的に割り当てられたメモリが格納されるメモリ領域です。クラスのインスタンスを作成すると、ヒープからメモリが割り当てられます。

Delphi プログラムでは、ヒープ メモリは次の場合に使用されます。

  • クラスのインスタンスを作成します。
  • 動的配列の作成とサイズ変更。
  • GetMem、FreeMem、New、および Dispose() を使用してメモリを明示的に割り当てます。
  • ANSI/wide/Unicode 文字列、バリアント、インターフェイスの使用(Delphi によって自動的に管理されます)。

ヒープメモリには、メモリのブロックを割り当てる順序があるような適切なレイアウトはありません。ヒープはビー玉の缶のように見えます。ヒープからのメモリ割り当てはランダムで、そこからのブロックよりもここからのブロックです。したがって、ヒープ操作はスタック上の操作よりも少し遅くなります。

新しいメモリ ブロックを要求すると(つまり、クラスのインスタンスを作成する)、Delphi メモリ マネージャがこれを処理します。新しいメモリ ブロックまたは使用済みで破棄されたメモリ ブロックを取得します。

ヒープはすべての仮想メモリ ( RAM とディスク容量 )。

メモリの手動割り当て

これでメモリに関するすべてが明確になったので、(ほとんどの場合) 安全に上記を無視して、昨日と同じように Delphi プログラムを書き続けることができます。

もちろん、いつ、どのように手動でメモリを割り当て/解放するかを知っておく必要があります。

「EStackOverflow」(記事の冒頭) が発生したのは、DoStackOverflow を呼び出すたびに、スタックから新しいメモリ セグメントが使用され、スタックに制限があるためです。それと同じくらい簡単です。