web-dev-qa-db-ja.com

変数はどのように言語コンパイラまたはインタプリタに格納されますか?

Pythonで変数を設定するとします。

five = 5

ブーム。これはどのように保存されていますか?コンパイラやインタプリタはそれをそのような変数に入れますか?

varname = ["five"]
varval  = [5]

これがどのように行われる場合、それはどこに保存されますか?これは永遠に続くようです。

8
baranskistad

通訳

通訳はあなたが推測した方法で働きます。単純なモデルでは、変数名を辞書キーとして、変数値を辞書値として1つの辞書を維持します。言語が特定のコンテキストでのみ表示される変数の概念を知っている場合、インタープリターは複数のディクショナリーを維持して、異なるコンテキストを反映します。インタプリタ自体は通常コンパイルされたプログラムであるため、そのストレージについては以下を参照してください。

コンパイラ

(これは、言語とコンパイラに大きく依存し、非常に単純化されているため、単にアイデアを提供するためのものです。)

たとえば、グローバル変数int five = 5があるとします。グローバル変数はプログラム内に1回だけ存在するため、コンパイラーはデータ域に4バイト(intサイズ)の1つのメモリー域を予約します。固定アドレス(1234など)を使用できます。コンパイラは、実行可能ファイルに、静的データメモリとして1234で始まる4バイトが必要であるという情報を配置します。プログラムの開始時およびオプションで(デバッガーサポート)1234の場所がfiveと呼ばれ、整数を含むという情報。他のコード行がfiveという名前の変数を参照している場合、コンパイラはそれが1234に配置されていることを記憶し、アドレス1234のメモリ読み取りまたは書き込み命令を挿入します。

int six = 6が関数内のローカル変数である場合、この関数の現在アクティブな呼び出しごとに1つ存在する必要があります(再帰またはマルチスレッドのために複数存在する可能性があります)。したがって、すべての関数呼び出しは、変数(six変数の4バイトを含む)を保持するのに十分なスペースをスタックにスタックします。コンパイラは、このスタックフレーム内のsix変数を配置する場所を決定します。フレームの先頭から8バイトで、その相対位置を記憶しているため、コンパイラーが関数に対して生成する命令は次のとおりです。

  • 関数のすべてのローカル変数に十分なバイトだけスタックポインタを進めます。

  • 数値6(sixの初期値)をスタックポインタの8バイト上のメモリロケーションに格納します。

  • 関数がsixを参照する場合は常に、コンパイラーはスタックポインターの8バイト上にあるメモリロケーションの読み取りまたは書き込み命令を挿入します。

  • 関数が終了したら、スタックポインタを古い値に巻き戻します。

繰り返しますが、これは非常に単純化されたモデルであり、すべての変数タイプをカバーしていませんが、理解を深めるのに役立つかもしれません...

13
Ralf Kleberhoff

それは実装に依存します。

たとえば、Cコンパイラはコンパイル中にシンボルテーブルを維持する場合があります。これは、各複合ステートメントの開始ブレース{が新しいローカル変数の新しいスコープを導入する可能性があるため、スコープのプッシュとポップを可能にする豊富なデータ構造です。行き来するスコープの処理に加えて、宣言された変数を記録し、それぞれに名前とそのタイプを含めます。

このシンボルテーブルのデータ構造は、名前による変数の情報の検索もサポートしています。識別子によって、コンパイラは宣言された変数情報を解析で見た生の識別子にバインドするときにこれを行うので、これはコンパイルのかなり早い段階で行われます。

ある時点で、コンパイラーは変数にロケーションを割り当てます。おそらく、場所の割り当ては同じシンボルテーブルのデータ構造に記録されます。コンパイラーは構文解析中に直接ロケーション割り当てを行うことができますが、構文解析後だけでなく、一般的な最適化後まで待機すると、より良いジョブを実行できる可能性があります。

ある時点で、ローカル変数の場合、コンパイラーはスタックの場所またはCPUレジスターを割り当てます(生成されたコードの一部とCPUのスタックの場所など、変数が実際に複数の場所を持つことができるため、より複雑になる場合があります)他のセクションに登録します)。

最後に、コンパイラーは実際のコードを生成します。コンパイルされるコードを実行するために必要に応じて、CPUレジスターまたは割り当てられたスタックの場所によって変数の値を直接参照する機械語命令です。ソースコードの各行は独自の一連のマシンコード命令にコンパイルされるため、生成された命令は演算(加算、減算)だけでなく、参照される変数の場所もエンコードします。

コンパイラーから出力される最終的なオブジェクトコードには、変数の名前と型はありません。場所、スタックの場所、またはCPUレジスタのみがあります。さらに、場所のテーブルはありませんが、これらの場所は、変数の値が格納されている場所を知っている各機械語命令によって使用されます。ランタイムコードで識別子を検索する必要はありません。生成されたコードの各ビットは、実行する操作と使用する場所を知っているだけです。

コンパイル中にデバッグが有効になると、コンパイラはシンボルテーブルの形式を出力するため、たとえば、デバッガはさまざまなスタックの場所にある変数の名前を認識できます。

他のいくつかの言語は実行時に動的に識別子を検索する必要があるので、そのようなニーズをサポートする何らかの形のシンボルテーブルも提供するかもしれません。


通訳には幅広いオプションがあります。それらは、実行中に使用するために(解析中の使用に加えて)シンボルテーブルのようなデータ構造を維持しますが、スタックの場所を割り当て/追跡する代わりに、変数の値をシンボルテーブルの変数エントリに関連付けて保存するだけです。データ構造。

シンボルテーブルは、スタックではなくヒープに格納されている可能性があります(スコープと変数にスタックを使用することは確かに可能であり、さらに、ヒープ内のスタックを模倣して、変数の値をそれぞれの近くにパックするというキャッシュに優しい利点を得ることができますその他)、コンパイラはスタックの場所を使用するので、インタプリタはおそらく変数の値を格納するためにヒープメモリを使用しています。一般的に言って、CPUレジスタはインタプリタ自体のコード行の実行でビジーであるため、インタプリタは変数の値のストレージとしてCPUレジスタを使用する自由もありません...

10
Erik Eidt