ランタイムスタックはメモリのデータセグメントに保持されますか?
質問
バックグラウンドで何が起こっているのかを実験し、それが本から得た小さな知識と明らかに一致していることがわかったので、スタック メモリの構成に非常に興味を持っています。私が理解したことが正しいかどうかを確認したかっただけです。
私は基本的なプログラムを持っています -- 2 つの関数があり、1 つは foo で、もう 1 つは main (エントリ ポイント) です。
void foo(){
// do something here or dont
}
int main(){
int i = 0;
printf("%p %p %p\n",foo, &i, main);
system("PAUSE");
return EXIT_SUCCESS;
};
プログラムの出力は以下のようになります。main のローカル変数 i はまったく関係のない位置にあります。integer は値型ですが、main に対してローカルな char * ポインターを使用して再度チェックすると、同様の結果が得られます。
00401390 0022FF44 00401396
Press any key to continue . . .
私は主に、コードと変数がメモリの異なるセグメント(コードセグメント/データセグメント)に割り当てられることを理解しています。では、基本的に、コール スタックは関数の実行に関する基本情報 (ローカル変数、パラメーター、戻り値) を折りたたんでデータ セグメントに保持していると言うのは正しいでしょうか?
解決
最初に少し注意してください:これらの答えはすべて、オペレーティング システムとハードウェア アーキテクチャによって多少影響を受けます。Windows は、UNIX のような言語、リアルタイム オペレーティング システム、および古い小規模システムの UNIX とはかなり根本的に異なる動作をします。
しかし、@richieと@paulが言ったような基本的な答えは「はい」です。コンパイラとリンカーがコードを通過すると、UNIXの「テキスト」および「データ」セグメントとして知られているものに分類されます。あ テキストセグメント 命令といくつかの種類の静的データが含まれます。データ セグメントにはデータが含まれています。
データ セグメントの大きなチャンクがスタックとヒープ領域に割り当てられます。他のチャンクは、静的データ構造や外部データ構造などに割り当てることができます。
したがって、プログラムが実行されると、プログラム カウンタはデータとは異なるセグメントから命令を忙しくフェッチします。ここで、いくつかのアーキテクチャの依存関係について説明しますが、一般に、セグメント化されたメモリがある場合、セグメントからのバイトのフェッチが可能な限り効率的になるように命令が構築されます。古い 360 アーキテクチャでは、 ベースレジスタ, x86 では、アドレス空間が古い 8080 年代から最新のプロセッサに移行するにつれて生えてきましたが、ご想像のとおり、命令のフェッチとそのオペランドが非常に集中的に使用されるため、すべての命令が非常に注意深く最適化されています。
現在は仮想メモリを備えたより現代的なアーキテクチャに移行しており、 メモリ管理ユニットs.現在、マシンには特定のハードウェアが搭載されており、プログラムがアドレス空間を大きなフラットな範囲のアドレスとして扱うことができます。さまざまなセグメントがそのビット仮想アドレス空間に配置されるだけです。MMU の仕事は、仮想アドレスを取得して物理アドレスに変換することです。これには、その仮想アドレスが現時点で物理メモリにまったく存在しない場合の対処も含まれます。繰り返しますが、MMU ハードウェアは非常に高度に最適化されていますが、だからといって、 いいえ 関連するパフォーマンスコスト。しかし、プロセッサが高速になり、プログラムが大きくなるにつれて、その重要性はますます低くなりました。
他のヒント
はい、そうです。コードとデータは、メモリのさまざまな部分に、さまざまな権限で存在します。スタックはパラメーターを保持し、アドレスとローカル(<!> quot; automatic <!> quot;)変数を返し、データとともに存続します。
はい。
コードメモリがROMで、データメモリがRAM(一般的な小さなチップアーキテクチャ)であるとします。次に、スタックがデータメモリにある必要があります 。
まあ、SPARCについて話すことができます:
はい。プログラムを実行すると、プログラムは2回読み取られます(少なくともSPARCでは)。プログラムはメモリにロードされ、その後、配列/スタックの割り当てがロードされます。プログラムの2回目のパスで、スタックは個別のメモリに割り当てられます。
CISCベースのプロセッサについてはわかりませんが、あまり変化しないと思います。
プログラムは、特に次の理由で未定義の動作を示します。
- コードをコンパイルする言語に応じて、
<stdio.h>
または<cstdio>
を含めない -
printf
およびすべての変数引数関数には、引数を型チェックする機能がありません。したがって、正しく型指定された引数を渡すことはあなたの義務です。本当にすべきです: -
system()
にはスコープ内に宣言がありません。場合によっては、<stdlib.h>
または<cstdlib>
を含めます。
コードを次のように記述します:
#include <stdio.h>
int main() {
/* ... */
printf("%p %p %p\n", (void *)foo, (void *)&i, (void *)main);
/* ... */
}
次の点にも注意してください:
-
void foo()
の定義は、CではなくC ++のプロトタイプです。ただし、void foo(void)
と記述すると、両方の言語でプロトタイプが得られます。 - <=>は実装に依存します。コードはプラットフォーム間で期待どおりに動作しない場合があります。
固有の言語(CまたはC ++)では、メモリの編成方法に制限はありません。スタックやヒープの概念さえありません。これらは、実装が適切と判断したときに定義されます。理想的には、実装によって提供されるドキュメントを参照して、それらが何を行うのかについて公正なアイデアを得る必要があります。