プログラムの実行中にデータはどこに存在するのでしょうか?
基本知識
- CPU: (中央処理装置/プロセッサ)、プロセッサとも呼ばれます。
- RAM: (ランダム アクセス メモリ)、ランダム アクセス メモリ。記憶とも呼ばれます。データは一時的にのみ保存されます。
- ROM:(Read Only Memory)「読み取り専用メモリ」と訳され、情報をランダムに読み出すことしかできず、新たな情報を書き込むことはできません。情報を長期間保存できるのが特徴です。メモリに書き込まれた後、停電によって情報が失われることはありません。マスク ROM (ハードウェアで固定化)、PROM (一度プログラムされる) などの従来の ROM タイプでは、製造またはプログラミング後に ROM の内容を変更することはできませんが、EEPROM (電気的に消去可能なプログラマブル読み取り専用メモリ) やフラッシュなどの最新の ROM では、 ROM (フラッシュ メモリ)。これらの ROM タイプでは、特定の条件下で消去および再プログラムが可能です。ROM のサイズは、数千バイト (KB) から数百 GB までさまざまです。
- スタック: (スタック)、スタックは連続ストレージのためのデータ構造です。先入れ後出しの原則に従ってデータを保存します。最初に入力されたデータはスタックの一番下にプッシュされ、最後のデータはスタックの最後に置かれます。スタックの先頭 データを読み取る必要がある場合、スタックから読み取られ、先頭からデータのポップが開始されます (最後のデータが最初に読み出されます)。
- ヒープ:(ヒープ)は不連続なツリーストレージデータ構造であり、各ノードは値を持ち、ツリー全体がソートされます。特徴は、ルートノードの値が最小(または最大)であり、ルートノードの2つのサブツリーもヒープであることです。これは、ランダム アクセスを使用して優先キューを実装するためによく使用されます。
- メモリ リーク: メモリ リークとは、プログラム内でのメモリの動的割り当て (malloc()、calloc()、new などの使用など) を指しますが、このメモリが不要になったときに正しい解放操作が実行されず、その結果、このメモリ内ではリサイクルして再利用することはできないため、メモリ リソースが無駄になります。プログラムがメモリを解放せずに占有し続けるため、プログラムの実行時間が増加するにつれて、使用可能なメモリが徐々に減少し、最終的にはプログラムがクラッシュしたり、システム リソースが枯渇したりする可能性があります。
- スタック フレーム: (スタック フレーム) は、関数呼び出し時に関数の実行に必要な情報を格納するために使用されるメモリ領域です。関数が呼び出されるたびに、コンピューター システムは関数のスタック フレームを作成し、それをスタック (関数呼び出しスタックまたは呼び出しスタックと呼ばれる) にプッシュして、関数のコンテキスト情報とローカル変数を保存します。スタック フレームには通常、次のものが含まれます。
- 戻りアドレス (戻りアドレス): 関数の実行後に関数を呼び出すコードの場所のアドレスを保存します。関数の実行後、戻りアドレスを介して呼び出し元に戻り、実行を継続します。
- パラメータ(引数):関数呼び出し時に渡されるパラメータ値を保存します。これらのパラメーター値は、関数の実行中に使用するためにスタック フレームにコピーされます。
- ローカル変数: 関数内で定義されたローカル変数の値を保存します。ローカル変数は、関数の実行中にのみ存在する関数内の変数です。
- 前のスタック フレーム ポインター: 前の関数のスタック フレームを指します。実行後に関数が呼び出し元に戻るときに、前のスタック フレームを見つけるために使用されます。スタック フレーム ポインタは特殊なレジスタまたはメモリの場所であり、関数呼び出し中にプログラムが実行を継続し、正しい関数呼び出しと戻りを実現するようにガイドするために使用されます。
※注意:リターンアドレスと前回のスタックフレームポインタの違い:
1. 保存のタイミングが異なります。以前のスタック フレーム ポインターの保存は、呼び出し元が呼び出される関数を呼び出す前に行われ、その目的は、関数呼び出しが終了した後に呼び出し元のスタック フレームに戻ることです。一方、リターン アドレスの保存は、関数呼び出し元が呼び出される関数を呼び出すときに行われます。その目的は、関数内 呼び出しが終了した後、呼び出し元の正しい実行ポイントに戻ります。
2.意味が異なります。戻りアドレスは、最終結果を返す場所を指し、最後のスタック フレーム ポインターは、プログラムの実行を継続する、つまり呼び出し元のコードの実行を継続するように指示します。
- プログラムを実行する手順:
- ソース コードの作成: プログラマーは、C、C++、Python などのプログラミング言語を使用してプログラムのソース コードを作成します。ソース コードは、人間が判読できる形式のプログラムです。
- コンパイル: ソース コードをコンパイラーでコンパイルし、コンピューターが理解して実行できるバイナリ マシン コードに変換する必要があります。コンパイラは、ソース コードを、特定のコンピュータ アーキテクチャ (x86、ARM など) に関連付けられたマシン コード ファイル、通常は実行可能ファイル (Windows の .exe、Linux の実行可能ファイルなど) に変換します。
- リンク: 外部ライブラリまたはその他のモジュールがプログラムで使用されている場合、コンパイラは関連するライブラリを実行可能ファイルにリンクして、最終的な実行可能ファイルを形成します。
- ロード: プログラムが実行されると、オペレーティング システムは実行可能ファイルをメモリにロードします。ロード時に、オペレーティング システムはプログラムに必要なメモリ領域を割り当て、プログラムを実行するための環境をセットアップします。
- 実行: プログラムがメモリにロードされると、コンピュータの中央処理装置 (CPU) がプログラムのマシンコードを命令ごとに実行します。プログラムの実行プロセスは、計算、条件判定、ループ、関数呼び出しなど、ソースコードのロジックに従って動作します。
- 実行結果: プログラムの実行結果は、プログラムの設計と出力方法に応じて、コンピューターのモニター、端末、または出力ファイルに表示されます。
- 終了: プログラムの実行が完了するか、特定の終了条件が発生すると、プログラムは自動的に実行を終了し、対応するリソースを解放します。
- 仮想アドレス空間: 仮想アドレス空間は、コンピュータ システム内の各プロセスによって認識される抽象メモリ アドレス空間です。これは、各プロセスに独立した連続したアドレス空間があると思わせる論理ビューを提供しますが、実際には、これらのアドレス空間は物理メモリやその他のリソースにマップされます。仮想アドレス空間のサイズは、実際の物理メモリ サイズよりもはるかに大きくなる可能性があるため、プロセスにより大きなアドレス空間とより柔軟なメモリ管理が提供されます。
- プログラムの実行コードは、通常、コンパイラによってソースコードから機械語コードや中間コードにコンパイルされ、実行ファイルが生成される。プログラムを実行すると、オペレーティング システムは実行可能ファイルからプロセスの仮想アドレス空間にコードをロードします。この仮想アドレス空間には、ヒープ、スタック、コードセグメント、データセグメントなどの領域が含まれます。
保管所
登録
- 最速のストレージ領域です。CPU の内部にあります。CPU にはレジスタの数が少なく、CPU が 64 ビットであると仮定すると、レジスタに格納できるのは 1 桁で、数値の範囲は 0 ~ 2 の 64 倍 -1 となります。ストレージは通常、オペレーティング システムによって使用されます。
スタック
-
読み取りおよび書き込み速度はレジスタに次いで 2 番目です。関数呼び出しとローカル変数を保存するために使用されるメモリ領域です。これはコンパイラによって自動的に管理され、特定の方法に従ってメモリが割り当てられます。プログラムが関数を呼び出すと、関数のローカル変数と関数呼び出しコンテキスト (呼び出し元関数のリターン アドレスやスタック フレーム情報など) に連続したメモリ空間が割り当てられます。
-
スタック メモリは「後入れ先出し」 (後入れ先出し、LIFO) の原則に従います。つまり、最後にスタックにプッシュされたデータが最初に破棄されます (データの読み取り順序は次のとおりです)。異なります。読み取られたデータはアドレスに従って直接読み取られ、後入れ先出し方式では読み取られません)。
-
たとえば、実行シーケンスは次のとおりです。
-
main() 関数が呼び出され、main() 関数のスタック フレームが作成されます。
-
main() 関数内で functionA() を呼び出すと、functionA() 関数のスタック フレームが作成され、functionA() のスタック フレームは main() のスタック フレームの上に配置されます。
-
functionA()でローカル変数aを宣言し、その値を出力します。
-
functionA() で functionB() を呼び出すと、functionB() 関数のスタック フレームが作成され、functionB() のスタック フレームは functionA() のスタック フレームの上に配置されます。
-
functionB()でローカル変数bを宣言し、その値を出力します。
-
functionB() が実行されると、そのスタック フレームは破棄されます。
-
functionA() が実行されると、そのスタック フレームは破棄されます。
-
main() 関数が実行され、そのスタック フレームが破棄され、プログラムが終了します。
この例では、functionB() がスタックにプッシュされますが、スタックの「後入れ先出し」の性質により、まずそれが破棄され、次に functionA() のスタック フレームが破棄されます。
#include <stdio.h> void functionB() { int b = 20; printf("Function B: %d\n", b); } void functionA() { int a = 10; printf("Function A: %d\n", a); functionB(); } int main() { functionA(); return 0; }
-
-
プログラムに必要なスタック メモリの量を知るにはどうすればよいでしょうか?
コンパイル段階で、コンパイラーはスタック フレームのサイズを推定しますが、必要なスタック メモリの量は予測しません。
コンパイラはコンパイル段階で関数のコードを分析し、各関数に必要なスタック フレームのサイズを計算します。このプロセスはスタック フレーム割り当て (スタック フレーム割り当て) と呼ばれ、スタック メモリを割り当てるための対応するコードを生成します。コンパイル時に。スタック フレーム サイズの計算は、関数のローカル変数とパラメーターのメモリ要件、関数呼び出し中に保存する必要があるコンテキスト情報などの要素に基づいて行われます。
スタック フレーム サイズの推定は静的な分析にすぎず、プログラムの実行時のメモリ要件を完全な精度で予測できない場合があります。
ヒープ
- RAM領域にあります。スタックとは異なり、コンパイラは、保存されたデータがヒープ上にどれだけ長く存在するかを知りません。
- ヒープ メモリの解放順序は「先入れ先出し」(FIFO) です。つまり、最初に割り当てられたメモリが最初に解放されます。
- ヒープは動的にメモリ サイズを割り当てることができ、実行時に動的にメモリを割り当てるため、ライフタイムをコンパイラに事前に伝える必要がありませんが、実行時に動的にメモリが割り当てられるため、アクセス速度が遅いという欠点があります。
- ヒープ メモリのサイズは、オペレーティング システムとコンピュータ ハードウェアの構成によって異なります。最新のコンピュータ システムでは、通常、各プロセスが使用できる仮想アドレス空間の量にハードウェア制限があります。この制限は、オペレーティング システムとハードウェアの両方によって決まります。
32 ビット オペレーティング システムでは、各プロセスの仮想アドレス空間は通常 2^32 (4GB) で、その一部はオペレーティング システム自体と一部のシステム カーネル領域に使用され、残りのアドレス空間はプロセス用に予約されます。 。したがって、32 ビット オペレーティング システムでは、ヒープ メモリのサイズは通常、数 GB 程度に制限されます。 - ヒープ メモリの割り当てと解放は通常、プログラマによって手動で制御され、通常は標準ライブラリ関数 (C 言語の malloc()、calloc()、realloc()、C++ new など) を使用した動的なメモリ割り当てを通じて実装されます。 .) ヒープ メモリを割り当てます。動的メモリ割り当て機能は、要求されたメモリ サイズを満たすのに十分な大きさの連続した空きメモリ領域をヒープ メモリ内で探します。適切なメモリ ブロックが見つかった場合は、使用済みとしてマークされ、このメモリへのポインタが返されます。そうでない場合は、特定の割り当て戦略に従って、オペレーティング システムにさらに多くのメモリ スペース (ヒープ拡張) が要求されます。次にプログラムに割り当てます。
常時保存
- 通常、コード内に直接保存されます。これは、定数の値がプログラミング段階で生成されたマシンコードに直接埋め込まれることを意味します。
例:#include <stdio.h> int main() { int x = 10; int y = 5; int result = x + y; printf("The result is: %d\n", result); return 0; }
上記のマシン コードでは、定数 10 と 5 はレジスタに直接ロードされ、実行時にメモリからこれらの定数の値を読み取る必要がなく、ADD 命令に追加されます。LOAD_CONST 10 ; 把常量 10 加载到寄存器 LOAD_CONST 5 ; 把常量 5 加载到寄存器 ADD ; 将寄存器中的两个值相加 STORE result ; 将结果存储到变量 result
- ROMに保存することも可能です。
非RAMストレージ
- ディスクなどの他のメディアに保存されます。
補充する
キャッシュメモリ
- (キャッシュ) はコンピュータ内の高速メモリで、CPU のデータへのアクセスを高速化するために、最も一般的に使用される命令とデータを一時的に保存するために使用されます。CPU の内部、メモリと CPU の間に位置します。読み取りはメモリよりも高速ですが、レジスタよりは遅くなります。
- CPU がデータにアクセスする必要がある場合、まずキャッシュを検索し、キャッシュ内にデータが見つかった場合をキャッシュ ヒット (Cache Hit) といい、CPU はより速くデータを取得できます。データがキャッシュにない場合、CPU はキャッシュ ミス (Cache Miss) と呼ばれるメイン メモリからデータをフェッチする必要があり、追加の遅延が発生します。
- サイズの範囲は数百 KB から数メガバイトです。
仮想メモリ
- 仮想メモリは、コンピュータ システムのメモリ管理のためのテクノロジです。これにより、アプリケーションは連続的に使用可能なメモリ (連続的で完全なアドレス空間) があると認識しますが、実際には、通常、メモリは複数の物理メモリ フラグメントに分割され、一部はデータ交換のために一時的に外部ディスク ストレージに保存されます。このテクノロジを使用するシステムでは、仮想メモリ テクノロジを使用しないシステムよりも大規模なプログラムの作成が容易になり、実際の物理メモリ (RAM など) をより効率的に使用できるようになります。
- 仮想メモリの主な機能は次のとおりです。
- 仮想アドレス空間。
- ページング メカニズム: 仮想メモリは、仮想アドレス空間をページと呼ばれる固定サイズのブロックに分割します。物理メモリも、物理ページ (ページ フレーム) と呼ばれる、ページと同じサイズのブロックに分割されます。仮想メモリと物理メモリ間のマッピングは、ページ テーブル (ページ テーブル) を通じて管理されます。
- ページ置換: プログラムが仮想アドレス空間内のページにアクセスすると、オペレーティング システムはそのページが既に物理メモリ内にあるかどうかを判断します。ページが物理メモリ上にある場合、これをページヒット(Page Hit)といい、プログラムは物理メモリ上のデータに直接アクセスできます。ページが物理メモリにない場合、それはページ フォールトと呼ばれます。オペレーティング システムは、新しいページをロードできるように、特定の置換アルゴリズムに従って物理メモリからページを選択して置換します。
- ページ スワップ: 物理メモリがすべてのアクティブなプログラムとデータを収容するのに十分でない場合、オペレーティング システムは非アクティブなページ (一時的に必要ではないページ) をスワップ スペース (スワップ スペース) と呼ばれるハード ディスク上の特別な領域にスワップできます。これにより、物理メモリが解放され、より必要なページを読み込むことができます。
- 16GB の仮想メモリ サイズは、各プログラムが論理的に 16GB の連続した仮想アドレス空間を持つことができることを意味します。このサイズは、オペレーティング システムとコンピューターのアーキテクチャに応じて、32 ビット システムまたは 64 ビット システムになります。