web-dev-qa-db-ja.com

コンパイラはどのようにメモリアドレスを変数に割り当てるのですか?

私は学生がプログラミングについて質問できるコースを教えています(!):私はこの質問を受けました:

なぜマシンは変数をメモリに入れるのかを選択するのですか?変数を格納する場所を指定できますか?

なんて言ったらいいのか分からない。これが私の最初の試みです:

コンパイラー(マシンではない)は、変数をプロセスのアドレス・スペースに自動的に保管する場所を選択します。 Cを使用すると、変数を格納する場所をマシンに指示できません。

しかし、それは「自動的に」いくらか非クライマックスであり、疑問を投げかけます...そして、コンパイラーかランタイムかOSか、誰が割り当てを行っているのかさえわからないことに気づきました。多分誰かが私よりも学生の質問によく答えることができます。

24
Dervin Thunk

変数のスコープ、サイズ、プログラミング環境に応じてメモリの割り当てにさまざまなアプローチがあるため、この質問への回答は非常に複雑です。

スタック割り当て変数

通常、_local variables_は「スタック」に置かれます。これは、コンパイラが「スタックポインタ」にオフセットを割り当てることを意味します。これは、現在の関数の呼び出しに応じて異なる場合があります。つまりコンパイラは、Stack-Pointer + 4、Stack-Pointer + 8などのメモリ位置がプログラムによってアクセスおよび使用可能であることを前提としています。関数からreturn- ingすると、メモリ位置はこれらの値を保持することが保証されません。

これは、次のようなアセンブリ命令にマップされます。 espはスタックポインター、_esp + N_はespに関連するメモリロケーションを参照します。

_mov eax, DWORD PTR SS:[esp]
mov eax, DWORD PTR SS:[esp + 4]
mov eax, DWORD PTR SS:[esp + 8]
_

ヒープ

次に、ヒープに割り当てられる変数があります。これは、標準ライブラリ(CではallocまたはC++ではnew)からメモリを要求するライブラリ呼び出しがあることを意味します。このメモリは、プログラムの実行が終了するまで予約されています。 allocおよびnewは、ヒープと呼ばれるメモリ領域内のメモリへのポインタを返します。割り当て関数は、ヒープ割り当てが遅くなる可能性があるメモリが予約されていないことを確認する必要があります。また、メモリが不足したくない場合は、もう使用されていないメモリをfree(またはdelete)する必要があります。標準ライブラリはメモリ内の使用済みおよび未使用の範囲と解放されたメモリの範囲を追跡する必要があるため、ヒープの割り当ては内部で非常に複雑です。したがって、ヒープに割り当てられた変数を解放することも、割り当てるよりも時間がかかる可能性があります。詳細については malloc()は内部的にどのように実装されているのですか? を参照してください

スタックとヒープの違いを理解することは、CおよびC++でプログラミングする方法を学ぶ上で非常に基本的です。

任意のポインタ

単純に、ポインタを任意のアドレス_int *a = 0x123_に設定することで、コンピュータのメモリ内の任意の場所をアドレス指定できると想定するかもしれません。 (CPUとシステムに応じて)プログラムはメモリをアドレス指定するときに厳しく制限されるため、これは正確には当てはまりません。

記憶を感じる

ガイド付きの教室での経験では、ソースコードをアセンブラーにコンパイルすることにより、いくつかの単純なCコードを探索することが有益な場合があります(gccはこれを行うことができます)。 int foo(int a, int b) { return a+b;}などの単純な関数で十分です(最適化なし)。次にint bar(int *a, int *b) { return (*a) + (*b);}のようなものを見てください。

Barを呼び出すとき、スタックに一度、mallocごとに1回、パラメーターを割り当てます。

結論

コンパイラーは、実行時にプログラム/標準ライブラリーによって取得されるbase-addressesに関連して、いくつかの可変プラットフォームとアライメントを実行します。

メモリ関連の質問の深い理解については、Ulrich Drepperの「すべてのプログラマがメモリについて知っておくべきこと」を参照してください http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.91.957

C-ish Country idenote以外

さらに、多くのスクリプト言語(Python、Perl、Javascript、LISP)やデバイスに依存しない環境(Java、C#)で人気のあるガベージコレクションもあります。これはヒープ割り当てに関連していますが、少し複雑です。

さまざまなプログラミング言語は、ヒープベース(スタックレスpython)または完全スタックベース(forth)のみです。

29
wirrbel

この質問に対する答えは、メモリ内のプログラムのレイアウトを理解することから始まると思います。オペレーティングシステムの下では、コンピューターのメインメモリは巨大な配列にすぎません。プログラムを実行すると、オペレーティングシステムはこのメモリのチャンクを取得し、次の目的で論理セクションに分割します。

  • スタック:このメモリ領域には、現在実行中の関数とそのすべての祖先を含む、現在スコープ内にあるすべての関数に関する情報が格納されます。保存される情報には、ローカル変数と、関数が完了したときに戻るアドレスが含まれます。

  • ヒープ:このメモリ領域は、ストレージを動的に割り当てるときに使用されます。通常、ローカル変数には、データが格納されているヒープ内のアドレス(つまり、ポインター)が含まれ、現在のデータが上書きされることを心配することなく、このアドレスをプログラムの他の部分に公開できます。関数が範囲外です。

  • データ、bss、テキストセグメント:これらは多かれ少なかれこの特定の質問の範囲外ですが、グローバルデータやプログラム自体などが格納されます。

お役に立てば幸いです。オンラインにもたくさんの優れたリソースがあります。私は「メモリ内のプログラムのレイアウト」をググったところ、これが見つかりました: http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory

7
danben

少し追加したいと思います。メモリマップと実行アドレスがわかっているファームウェア、および独自のリンカースクリプトを使用してソースをコンパイルするファームウェアの場合

セクション属性を使用してカスタムセクションを変数に割り当て、リンカースクリプトを使用して特定のアドレスをカスタムセクションに割り当てることができます。次に、変数は固定/割り当てられたアドレスを取得します。

関数内のローカル変数は、通常、関数のスタックフレーム内で順番に配置されます。

0
SLaks