メモリ使用ケース、スタックおよびヒープ:基本的なメモリ管理
前のセクションでは、 ISAの抽象化では、プログラムの「状態」はレジスタとメモリで完全に表されることを学びました(これは完全に真ではないが、今のところ心配する必要はありません)。 つまり、その内容がプログラムの動作を完全に決定します。 また、メモリはレジスタよりもはるかに大きいため、プログラムが状態を格納する主要なストレージです。
ただし、ISAはメモリの使用方法に関するガイドラインを提供しませんでした。 これにより柔軟性が増しましたが、複雑さも増しました。 通常、プログラムはどのメモリアドレスがどの値を格納しているかを知る必要があります。 特定のアドレスが特定の値を格納していると仮定しているが、そのアドレスが以前の命令で実際に上書きされていた場合、期待どおりに動作しません。
ソフトウェアアプリケーションがますます大きくなるにつれて、 メモリのどのアドレスが何の値を格納しているかを手動で追跡することがますます困難になり、メモリの読み書きが正しく機能することを確認することが困難になります。 これがメモリ管理の問題と呼ばれています。
この問題に対処するために、人々は「メモリの使用方法」についてさまざまなモデルを提案してきました。 例えば、所有権、自動ガベージコレクション、RAII(リソースの割り当ては初期化)などです。 ここではすべてを説明しませんが、ほとんどのモデルが使用するか、基礎となる共通のコンセプトを説明します。 これらのコンセプトは、メモリ使用ケース、スタック、およびヒープです。
これらのコンセプトは、特にガベージコレクタを持つ高レベルの言語(たとえばPython)ではしばしば隠されて表示されず、抽象化されています。 しかし、これらのコンセプトを理解することは、プログラムが低レベルで実行される方法を理解するための良い基盤を提供します (ISAと同様)。
メモリ使用ケース
メモリ使用ケースは、次の質問に答えるのに役立ちます:あるメモリの一部について (すなわち、一連のメモリアドレスに対応するメモリ内のスポット)を、特定の時間の間に、 どのように使用するか、すなわち、このメモリの一部はそれぞれの時点で何のために使用されるのか?
メモリ使用ケースとしての抽象化
前のモジュールでの抽象化の定義を思い出すと、メモリ使用ケースも抽象化です: メモリ使用ケースは、メモリが何であるかを定義します (ある期間の間に特定のユースケースで使用できるもの) 誰か(プログラマ)にとっては、関係のない詳細を無視して。
当然の考えのように思われるかもしれませんが、メモリ使用ケースだけでメモリ管理の多くの問題を解決できます。 なぜなら、メモリアドレスに対して「どうすべき」で「どうすべきでない」ことが明確に述べられるからです。
メモリの使用ケースを定義する前に、プログラマはメモリの一部を見つめて、何をすべきかわからないままになります。 「ここにデータを書いてもいいのだろうか?ここにはどの値が保存されているのだろうか? ランダムなバイトを上書きすることができるのか、意味のある内容を保持すべきなのか? あとで何らかの命令がこのメモリの一部を使用するのだろうか? ここに永続したものを格納したいと思った場合、ある時点で上書きされるのだろうか?」 プログラムが大きくなるにつれて、これらの質問に答えることはますます困難になります。 なぜなら、それにはプログラムのすべての命令を見て、メモリをどのように操作するかを調べる必要があるからです。
メモリ使用ケースがあれば、はるかに簡単になります。 メモリの使用ケースを知った後、開発者はそのメモリを使用するための有効な方法と、特定の方法で使用した場合に何が起こるかについてのいくつかの仮定を持つことができます。 たとえば、開発者は次のように考えるかもしれません。 「OK、この時点では、このメモリの一部には重要なものが入っていません。 だから、何でも書いてもかまいません。 ただし、操作Aが終わった後、操作Bはこのメモリを使用してレコードを保存するため、 ここに書いた内容は上書きされますので、操作Bが開始した後にこのメモリの一部を読み取らないように気をつけなければなりません。」
メモリ使用ケースに関連する用語
以下に、メモリ使用ケースに関連するいくつかの用語を説明します。 開発者の世界ではよく遭遇するでしょう。
アロケート
「アロケート」とは、メモリの一部を「空き」メモリ(つまり、誰も使用していないメモリ)を見つけて、あるユースケースのために目的地を探すことを意味します。 メモリの一部が何らかのユースケースに割り当てられた後、それは他の何にも使用しないことが考えられます。
たとえば、コンピュータプログラムでは、誕生日などのデータを格納するためにメモリを割り当てることは一般的です。
デアロケート
「デアロケート」とは、それに関連付けられた現在のユースケース(何であれ)の終了をマークすることを意味します。 メモリの一部がデアロケートされると、以前のユースケースに使用することは許可されなくなり、他の任意のユースケースに再利用することが許可されます。
たとえば、現在誰かの誕生日を格納しているメモリの一部がデアロケートされると、\(153 \times 13251\)の結果などを格納するために使用することは問題ありません。
「デアロケート」という用語は「解放」または「破棄」と同義語として使用されることもあります。
スタックとヒープ
メモリ使用ケースの概念に基づいて、スタックとヒープはメモリ管理のより明確なモデルを作り出すための2つの特別なメモリ領域です。 スタックとヒープは、特定のメモリ使用ケース(誕生日のような特定のものを格納すること)ではありません。 むしろ、メモリをより明確な方法でアロケーションできる「プール」です。
スタックとヒープは、プログラムの全体の寿命の間存在することができます。
スタックとヒープとしてのデータ構造
スタックとヒープは、データ構造の名前でもあります。 ここで紹介したメモリオブジェクトといくつかの類似点を共有していますが、同じものではありません。
「スタックメモリオブジェクト」と「スタックデータ構造」の明確化に関しては、前者は通常「スタックメモリ」と呼ばれますが、後者は単に「スタック」と呼ばれます。 同様に、「ヒープメモリオブジェクト」と「ヒープデータ構造」は通常「ヒープメモリ」と単に「ヒープ」と呼ばれます。
スタック
メモリ使用ケースの観点に立てば、スタックは特別な、通常小さなメモリ領域であり、おおよそ「小さくて迅速にアクセスする必要があるもの」を格納するために使用されます。
スタックは、Last-In-First-Out (LIFO)の順序です。 つまり、メモリをスタックからアロケートすると、最新にアロケートされたメモリが最後にアロケートされた以前のメモリの上にあることになります。 同様に、スタックからメモリをデアロケートすると、一番上のメモリが最初にデアロケートされます。
なぜスタックは「小さくて迅速にアクセスする必要があるもの」に適しているのでしょうか?
スタックは通常小さく、小さなものしか格納できません。 一方で、スタックはアクセスが迅速であるために、迅速にアクセスする必要があるものに適しています。 アクセスしたいものの次にあるもののサイズと、スタックの一番上(最後の要素のアドレス)のメモリアドレスを知っている限り、 アクセスしたいもののメモリアドレスを簡単に計算してアクセスできます。 C++などのコンパイルされた言語では、このようなメモリアドレスの計算は通常コンパイル時に静的に行われます。 つまり、プログラムが実行されるとき、スタック上のアクセスしたいもののアドレスをほとんど計算せずに取得できます (現時点では、これがどのように機能するかを理解する必要はありません。 ただ覚えておくことは、スタック上の要素にアクセスすることは簡単で高速であるということです)。
おもしろい事実
スタックは小さく、プログラムの寿命中に通常固定されています。 スタックが提供するメモリよりも多くのメモリを使用しようとすると、 「スタックオーバーフロー」という例外が発生します。 スタックオーバーフローは、プログラムのエラーとその修正について話す プラットフォーム の名前でもあります。
ヒープ
メモリ使用ケースの観点からは、ヒープは特別な、通常大きなメモリ領域であり、おおよそ「大きな、長持ちするもの」を格納するために使用されます。 ヒープは通常スタックよりもはるかに大きいです。
ヒープは順不同です。これにより、スタックと比較してメモリのアロケーションとアクセスがどちらも困難になります。 スタックではメモリは常に一番上に割り当てられますが、ヒープではまず空きメモリを見つけてから割り当て、 アクセスする場合はそのメモリのアドレスを知っている必要があります。そうでなければ、ヒープ上でそれを見つけるのは非常に困難です。
コンピュータプログラムでは、一般的な組み合わせは、大きなものをヒープに格納し、そのメモリアドレスをスタックに保持することです。 これにより、大きなもの(データベースのようなもの)を格納できます(ヒープは大きいので)が、それらに相対的に迅速にアクセスできます(スタックは高速なので)。
結論
このセクションでは、メモリ管理の基本的なモデルであるメモリ使用ケース、スタック、およびヒープについて説明しました。
重要なポイント:
- メモリ使用ケースは、ある時点でのメモリの用途を定義します。
- スタックは、小さく、順序が保証されたメモリ領域で、メモリのアロケーションとアクセスが迅速で簡単です。
- ヒープは順不同のメモリ領域です。スタックと比較してヒープからのメモリの割り当てには時間がかかりますが、ヒープはスタックよりもはるかに大きいです。
AI プロンプトのサンプル
このセクションのトピックに興味がある場合は、ChatGPTなどのAIに相談して、さらに詳しい情報を取得することができます。
始めるためのいくつかのサンプルプロンプト:
- スタックとヒープメモリの違いを説明してください。