これは実際にはシステムに依存しますが、仮想メモリを備えた最新のOSはプロセスイメージをロードし、次のようなメモリを割り当てる傾向があります。
+---------+
| stack | function-local variables, return addresses, return values, etc.
| | often grows downward, commonly accessed via "push" and "pop" (but can be
| | accessed randomly, as well; disassemble a program to see)
+---------+
| shared | mapped shared libraries (C libraries, math libs, etc.)
| libs |
+---------+
| hole | unused memory allocated between the heap and stack "chunks", spans the
| | difference between your max and min memory, minus the other totals
+---------+
| heap | dynamic, random-access storage, allocated with 'malloc' and the like.
+---------+
| bss | Uninitialized global variables; must be in read-write memory area
+---------+
| data | data segment, for globals and static variables that are initialized
| | (can further be split up into read-only and read-write areas, with
| | read-only areas being stored elsewhere in ROM on some systems)
+---------+
| text | program code, this is the actual executable code that is running.
+---------+
これは、多くの一般的な仮想メモリシステムの一般的なプロセスアドレス空間です。「ホール」とは、メモリ全体のサイズから、他のすべての領域が占めるスペースを引いたサイズです。これは、ヒープが成長するための大量のスペースを提供します。これは「仮想」でもあります。つまり、変換テーブルを介して実際のメモリにマップされ、実際のメモリ内の任意の場所に格納されます。これは、あるプロセスが別のプロセスのメモリにアクセスするのを防ぎ、各プロセスが完全なシステムで実行されていると見なすために、この方法で行われます。
一部のシステムでは、たとえば、スタックとヒープの位置が異なる順序になっている場合があることに注意してください( Win32の詳細については、以下のBilly O'Nealの回答を参照してください)。
他のシステムは非常に異なる場合があります。たとえば、DOSはリアルモードで実行され、プログラムの実行時のメモリ割り当ては大きく異なります。
+-----------+ top of memory
| extended | above the high memory area, and up to your total memory; needed drivers to
| | be able to access it.
+-----------+ 0x110000
| high | just over 1MB->1MB+64KB, used by 286s and above.
+-----------+ 0x100000
| upper | upper memory area, from 640kb->1MB, had mapped memory for video devices, the
| | DOS "transient" area, etc. some was often free, and could be used for drivers
+-----------+ 0xA0000
| USER PROC | user process address space, from the end of DOS up to 640KB
+-----------+
|command.com| DOS command interpreter
+-----------+
| DOS | DOS permanent area, kept as small as possible, provided routines for display,
| kernel | *basic* hardware access, etc.
+-----------+ 0x600
| BIOS data | BIOS data area, contained simple hardware descriptions, etc.
+-----------+ 0x400
| interrupt | the interrupt vector table, starting from 0 and going to 1k, contained
| vector | the addresses of routines called when interrupts occurred. e.g.
| table | interrupt 0x21 checked the address at 0x21*4 and far-jumped to that
| | location to service the interrupt.
+-----------+ 0x0
DOSが保護なしでオペレーティングシステムのメモリへの直接アクセスを許可したことがわかります。つまり、ユーザー空間プログラムは通常、好きなものに直接アクセスしたり上書きしたりできます。
ただし、プロセスアドレス空間では、プログラムは同じように見える傾向があり、コードセグメント、データセグメント、ヒープ、スタックセグメントなどとしてのみ記述され、少し異なってマッピングされました。しかし、ほとんどの一般的な領域はまだそこにありました。
プログラムと必要な共有ライブラリをメモリにロードし、プログラムの一部を適切な領域に配布すると、OSはメインメソッドがどこにあってもプロセスの実行を開始し、プログラムはそこから処理を引き継ぎ、必要に応じてシステムコールを実行しますそれが必要です。
スタックレスシステム、ハーバードアーキテクチャシステム(コードとデータが別々の物理メモリに保持される)、実際にBSSを読み取り専用メモリに保持するシステム(最初はプログラマーなど)しかし、これは一般的な要点です。
あなたが言った:
また、コンピュータープログラムは2種類のメモリを使用することも知っています。スタックとヒープは、コンピューターのプライマリメモリの一部でもあります。
「スタック」と「ヒープ」は、(必ずしも)物理的に異なる「種類」のメモリではなく、単なる抽象的な概念です。
スタックは、単に後入れ先出しデータ構造です。x86アーキテクチャでは、末尾からのオフセットを使用してランダムにアドレス指定できますが、最も一般的な機能は、それぞれにアイテムを追加および削除するPUSHおよびPOPです。これは一般に、関数ローカル変数(いわゆる「自動ストレージ」)、関数引数、戻りアドレスなどに使用されます(詳細は下記)。
「ヒープは、」オンデマンドで割り当てることができ、そして(あなたが直接それに任意の場所にアクセスすることができ、意味)ランダムにアドレス指定されたメモリのチャンクのためだけのニックネームです。これは通常、実行時に割り当てるデータ構造に使用されます(C ++では、new
およびを使用しdelete
、malloc
Cのフレンドなど)。
x86アーキテクチャでは、スタックとヒープはどちらも物理的にシステムメモリ(RAM)に常駐し、仮想メモリの割り当てを通じて上記のようにプロセスアドレス空間にマップされます。
レジスタは、(まだx86の上の)CPUの指示に応じて、物理プロセッサ(RAMとは対照的に)内部に存在し、テキスト領域から、プロセッサによってロードされる(そして、メモリ又は他の場所の他の場所からロードすることができます実際に実行されます)。それらは基本的に、非常に小さく、非常に高速なオンチップメモリの場所であり、さまざまな目的で使用されます。
レジスターのレイアウトはアーキテクチャーに大きく依存しているため(実際、レジスター、命令セット、およびメモリーのレイアウト/デザインは、まさに「アーキテクチャー」が意味するものです)、そのため、ここでは詳しく説明しませんが、それらをよりよく理解するためのアセンブリ言語コース。
あなたの質問:
スタックはどの時点で命令の実行に使用されますか?命令は、RAM、スタック、レジスターに行きますか?
スタック(それらを使用しているシステム/言語)は、次のように最もよく使用されます。
int mul( int x, int y ) {
return x * y; // this stores the result of MULtiplying the two variables
// from the stack into the return value address previously
// allocated, then issues a RET, which resets the stack frame
// based on the arg list, and returns to the address set by
// the CALLer.
}
int main() {
int x = 2, y = 3; // these variables are stored on the stack
mul( x, y ); // this pushes y onto the stack, then x, then a return address,
// allocates space on the stack for a return value,
// then issues an assembly CALL instruction.
}
このような単純なプログラムを作成し、それを(gcc -S foo.c
GCCにアクセスできる場合は)アセンブリにコンパイルして、見てみましょう。組み立ては非常に簡単です。スタックが関数のローカル変数、関数の呼び出し、引数と戻り値の格納に使用されていることがわかります。これは、次のようなことをする理由でもあります。
f( g( h( i ) ) );
これらはすべて順番に呼び出されます。それは文字通り、関数呼び出しとその引数のスタックを構築し、それらを実行してから、巻き戻し(または;)したときにそれらをポップアウトしています。ただし、前述のように、スタック(x86上)は実際にはプロセスメモリ空間(仮想メモリ内)にあるため、直接操作できます。実行中の個別のステップではありません(または少なくともプロセスに直交しています)。
参考までに、上記はC呼び出し規約であり、C ++でも使用されています。他の言語/システムは、引数を別の順序でスタックにプッシュする場合があり、一部の言語/プラットフォームはスタックを使用せず、さまざまな方法で処理を行います。
また、これらは実行中のCコードの実際の行ではないことに注意してください。コンパイラーはそれらを実行可能ファイルの機械語命令に変換しました。 次に、それらは(通常)TEXT領域からCPUパイプラインにコピーされ、次にCPUレジスタにコピーされ、そこから実行されます。 [これは誤りでした。以下のBen Voigtの訂正を参照してください。]