23

私は最近、Dragon Book を全部読みました (ただの楽しみで、実際のコンパイラを実際に実装するつもりはありません) が、この大きな疑問が頭の中にぶら下がっていました。

コンパイラーとインタープリターの実装の違いは何ですか?

私にとって、コンパイラは次のもので構成されています。

  • レクサー
  • パーサー (構文ツリーを構築する)
  • 中間コードの生成 (3 アドレス コードなど)
  • 必要に応じて、これらすべてのクレイジーなことを最適化してください:-)
  • 3つのアドレスコードから「アセンブリ」または「ネイティブコード」を生成します。

さて、明らかに、インタープリターにもコンパイラーと同じレクサーとパーサーがあります。
しかし、それはその後何をしますか?

  • 構文ツリーを「読み取り」、直接実行しますか? (ツリー内の現在のノードを指し示す命令ポインタを持つようなもので、実行は 1 つの大きなツリー トラバーサルとコール スタックのメモリ管理です) (もしそうなら、どのようにそれを行うのですか?実行は、ノードのタイプをチェックする巨大な switch ステートメントよりも優れています)

  • 3 つのアドレス コードを生成し、それを解釈しますか? (もしそうなら、それはどのように行うのですか? 繰り返しますが、1 マイルの長い switch ステートメントよりもエレガントなものを探しています)

  • 実際のネイティブ コードを生成し、メモリにロードして実行しますか? (その時点で、それはもはやインタープリターではなく、JITコンパイラーに似ていると思います)

また、「仮想マシン」の概念はどの時点で割り込まれますか? 言語で仮想マシンを何に使用しますか? (私の無知のレベルについて明確にするために、私にとって仮想マシンはVMWareです。VMの概念がプログラミング言語/プログラムの実行にどのように適用されるかわかりません)。

ご覧のとおり、私の質問は非常に広範囲です。私は主に、どの方法が使用されているかだけでなく、最初に大きな概念を理解し、次にそれがどのように機能するかを詳細に理解するために探しています. 私は醜い生の詳細が欲しい. 明らかに、これは、ここでこれらすべての詳細に回答することを期待するのではなく、読むべきものへの参照の探求です.

ありがとう!
ダニエル


編集:これまでにご回答いただきありがとうございます。しかし、私のタイトルが誤解を招くことに気づきました。コンパイラとインタープリタの「機能的な」違いを理解しています。
私が探しているのは、インタープリターとコンパイラーの実装方法の違いです。
コンパイラがどのように実装されているかを理解しました。問題は、インタプリタがそれとどのように異なるかです。

例: VB6 は明らかにコンパイラとインタプリタの両方です。コンパイラの部分がわかりました。しかし、IDE 内で実行しているときに、任意の時点でプログラムを停止し、コードを変更して、新しいコードで実行を再開する方法を理解できません。これはほんの一例であり、私が探している答えではありません。以下で説明するように、私が理解しようとしているのは、解析ツリーを作成した後に何が起こるかです。コンパイラは、「ターゲット」言語でそれから新しいコードを生成します。通訳者は何をしますか?

ご協力ありがとうございました!

4

10 に答える 10

8

コンパイラは、あるプログラミング言語のプログラムを別のプログラミング言語のプログラムに変換するプログラムです。それだけです - 単純明快です。

インタープリターは、プログラミング言語をその意味的な意味に翻訳します。

x86 チップは、x86 機械語のインタープリターです。

Javac は、Java 仮想マシンに対する Java のコンパイラです。実行可能アプリケーションである java は、jvm のインタープリターです。

一部のインタープリターは、ある言語を解釈しやすい別の内部言語に翻訳するという点で、コンパイルのいくつかの要素を共有しています。

インタープリターは通常、常にではありませんが、read-eval-print ループを備えています。

于 2009-01-24T01:01:39.663 に答える
8

プログラムは、実行したい作業の記述です。

コンパイラは、高レベルの記述をより単純な記述に変換します。

通訳者は、何をすべきかの説明を読み、作業を行います。

  • 一部のインタープリター (たとえば、Unix シェル) は、一度に 1 つの小さな部分の説明を読み取り、各部分を参照しながら処理を行います。一部 (Perl、Python など) は、説明全体を読み取り、内部的により単純な形式に変換してから、それに基づいて動作します。
  • 一部のインタープリター (Java の JVM や Pentium 4 チップなど) は、人間が直接操作するには退屈すぎる非常に単純な記述言語しか理解できないため、人間はコンパイラーを使用して高レベルの記述をこの言語に変換します。

コンパイラは決して仕事をしません。通訳者は常に仕事をします。

于 2009-01-24T04:04:18.327 に答える
7

簡潔な答え:

  • コンパイラは、後で実行するためにソースコードを実行可能な形式に変換します
  • インタプリタがソースコードを評価して即時実行

どちらをどのように実装するかについては、かなりの余地があります。インタプリタがネイティブ マシン コードを生成して実行することは可能ですが、仮想マシンのコンパイラはマシン コードの代わりに P コードを生成する場合があります。Forth のようなスレッド化されたインタープリター言語は、辞書でキーワードを検索し、関連するネイティブ コード関数をすぐに実行します。

コンパイラーは、コードを調べて後で実行するファイルを生成する時間が増えるため、一般的に最適化を改善できます。インタープリターは、コードを一目で「そのまま」実行する傾向があるため、最適化する時間が少なくなります。

バックグラウンドで最適化されたインタープリター、コードを実行するためのより良い方法を学習することも可能です

要約: 違いは、「後で実行するためにコードを準備する」または「コードを今すぐ実行する」ことに帰着します。

于 2009-01-24T01:24:27.357 に答える
4

どちらにも多くの共通点があり (字句パーサーなど)、違いについては意見の相違があります。私はこのように見ています:

古典的な定義では、コンパイラはシンボルのストリームを解析し、CPU で実行できるバイトのストリームに変換しますが、インタプリタは同じことを行いますが、ソフトウェアの一部で実行する必要がある形式に変換します (たとえば、 JVM、CLR)。

しかし、人々は「javac」をコンパイラーと呼んでいるため、コンパイラーの非公式な定義は、ソースコードに対して別のステップとして実行する必要があるものですが、インタープリターには「ビルド」ステップがありません (PHP、Perl など)。

于 2009-01-24T00:53:18.103 に答える
3

以前ほど明確ではありません。以前は、解析ツリーを構築し、バインドし、実行していました (多くの場合、最後の 1 秒でバインドします)。

BASIC はほとんどこの方法で行われました。

JIT を実行せずにバイトコード (java/.net) を実行するものはインタープリターであると主張できますが、バイトコードに「コンパイル」する必要があるため、従来の意味ではそうではありません。

古い学校の違いは次のとおりです。CPU コードを生成する場合、それはコンパイラです。編集環境で直接実行し、編集中に操作できる場合、それはインタープリターです。

それは実際のドラゴンの本よりもはるかに形式的ではありませんでしたが、参考になれば幸いです.

于 2009-01-24T00:54:09.040 に答える
2

あなたの質問のこの部分に関して、他の答えは実際には対処していません:

また、「仮想マシン」の概念はどの時点で取り入れられますか?言語で仮想マシンを何に使用しますか?

JVMやCLRのような仮想マシンは、VMで実行するためにコンパイルされた完全に異なる言語に対して、JITコンパイラの最適化、ガベージコレクション、およびその他の実装の詳細を再利用できるようにする抽象化レイヤーです。

また、言語仕様を実際のハードウェアからより独立させるのにも役立ちます。たとえば、Cコードは理論的には移植可能ですが、実際に移植可能なコードを作成する場合は、エンディアン、型のサイズ、変数の配置などについて常に心配する必要があります。Javaの場合、JVMはこれらの点で非常に明確に指定されているため、言語設計者とそのユーザーはそれらについて心配する必要はありません。指定された動作を実際のハードウェアに実装するのは、JVM実装者の仕事です。

于 2009-01-26T15:14:33.567 に答える
2

私の経験が何かを示している場合。

  1. インタープリターは AST をさらに削減/処理しようとはしません。コードのブロックが参照されるたびに、関連する AST ノードがトラバースされて実行されます。コンパイラーは、特定の場所で実行可能コードを生成し、それを処理するためにブロックを最大数回トラバースします。
  2. インタプリタのシンボル テーブルは値を保持し、実行中に参照されます。コンパイラのシンボル テーブルは変数の位置を保持します。実行中にシンボルテーブルのようなものはありません。

ショットでは、違いは次のように単純かもしれません

case '+':
    symtbl[var3] = symtbl[var1] + symtbl[var2];
    break;

の間に、

case '+':
    printf("%s = %s + %s;",symtbl[var3],symtbl[var1],symtbl[var2]);
    break;

(別の言語または (仮想) マシン命令をターゲットにするかどうかは問題ではありません。)

于 2009-01-24T01:24:00.867 に答える
1

解析ツリーが利用可能になると、いくつかの戦略があります。

1) ASTを直接解釈 (Ruby、WebKit独自のインタープリター) 2) コード変換 → バイトコードまたはマシンコードへ

エディット コンティニュを実現するには、プログラム カウンターまたは命令ポインターを再計算して移動する必要があります。小さな黄色い矢印の前後にコードが挿入されている可能性があるため、これには IDE の協力が必要です。

これを行う 1 つの方法は、プログラム カウンターの位置を解析ツリーに埋め込むことです。たとえば、「break」という特別なステートメントがあるかもしれません。プログラム カウンタは、実行を継続するために「ブレーク」命令の後に配置する必要があるだけです。

さらに、現在のスタック フレーム (およびスタック上の変数) について何をしたいかを決定する必要があります。おそらく、現在のスタックをポップして変数をコピーするか、スタックを保持しますが、GOTO と RETURN を現在のコードにパッチします。

于 2009-02-04T00:29:14.373 に答える
0

ステップのリストを考えると:

  • レクサー
  • パーサー(構文ツリーを構築します)
  • 中間コード(3番地コードなど)を生成する
  • 必要に応じて、これらすべてのクレイジーなことを最適化してください:-)
  • 3番地コードから「アセンブリ」または「ネイティブコード」を生成します。

非常に単純なインタプリタ(初期のBASICやTCLなど)は、一度に1行と2行のステップしか実行しませんでした。そして、実行される次の行に進む間、ほとんどの結果を破棄します。他の3つのステップはまったく実行されません。

于 2009-01-26T15:12:25.753 に答える