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


8

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

five = 5

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

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

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

回答:


13

通訳

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

コンパイラ

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

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

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

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

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

  • 関数がを参照するところはどこでも、sixコンパイラはスタックポインタの8バイト上にあるメモリ位置の読み取りまたは書き込み命令を挿入します。

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

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


私の知識レベルの誰かにとって理解しやすいです。本当にありがとう!あなたは本当にこれにいくつかの光を当てます!
baranskistad 2017

コンパイラがこの全体をモノリシックバイナリにコンパイルするのではなく、むしろ小さなコンパイルユニット(たとえば、ソースファイルごとに1つ)である場合、さらにおかしくなります。次に、すべての外部のもの(グローバルなど)は、リンカーと呼ばれる個別のツール(これらのすべての小さな「オブジェクト」ファイルを1つの大きなバイナリに組み合わせる)によって、後でアドレスを解決する必要があります。
Kat、

また、Cの古いバージョンに精通している場合、ローカル変数の動作の性質により、Cがすべてのローカル変数を関数の先頭で宣言する必要があった理由が明らかになります。後で、関数全体を最初に解析するだけで十分であると判断し、最終的に変数宣言を必要な場所に配置できるようにしました。
Kat

最後に、コンパイラーでローカル変数の辞書を使用するのを妨げるものがないこともおそらく関連があります。これは確かに動的型付けをはるかに容易にします(動的型はコンパイル時に既知のサイズがないため、スタックに完全に格納することはできません)。単に辞書は遅いです。ちなみに、ほとんどのコンパイル済み言語が静的型付けを使用する理由。
Kat 2017

10

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

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

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

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

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

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

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

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

他の一部の言語では、実行時に識別子を動的に検索する必要があるため、そのようなニーズをサポートするために何らかの形のシンボルテーブルを提供する場合もあります。


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

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


-3

コードがコンパイルされる対象を理解する最良の方法は、コードをアセンブリコンパイルすることです。アセンブリコードは、実行されているプロセッサ命令に最も近いものです。


1
OPはコンパイルされたコードについて質問していないようですが、コンパイラーがコードを解析しているときにコンパイル中に何が起こっているのでしょう。
user1118321 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.