インタープリタとコンパイラ

プログラミング言語で組んだプログラムをコンピュータで実現するとき、その実行方法には、大きく分けて二つの方法があります。それが、インタープリタ方式とコンパイラ方式です。インタープリタとは「解釈するもの(Interpreter)」というような意味の英語です。コンパイラ(Compiler)の意味は難しいですね。英語の辞書も大抵翻訳を放棄して、カタカナ書きです。語の成り立ちからすると「束ねて積み込むもの」というイメージでしょうか(想像するに、穴空きカードでプログラミングしていた時代の名残じゃないでしょうか)。

ソフトェア/ハードウェア

インタープリタにしてもコンパイラにしても、そう呼ばれるのは、その機能を持ったソフトウェア(アプリケーション)のことです。そういえば、ソフトウェアとハードウェアの話をしていませんでした。ついでにここで触れると、コンピュータとか機械のことはハードウェア(Hardware)と呼ばれ、それを動かすためのコードやデータの中身(コードは機械への命令ですが、これも大きく括れば計算機に食わせるデータの一つ)をソフトウェア(Software)といいます。機械は硬い(ハード:hard)ので、それとの対比でソフトなわけで、別に柔らかいわけじゃありません、っていうか、厳密にいうと手では触れません、ソフトウェアには。

さてインタープリタ方式とコンパイラ方式に話を戻します。前回も触れたように、機械の中では、普通の人間にはわけが分からない電気信号(機械語)の組み合わせが飛び交っているに過ぎません。これを人にもわかりやすいようにするためにプログラミング言語があるわけです。ですが、今度は機械の方でプログラミング言語を直接理解(つまり、対応した実行。機械が何かを"理解"することは、今のところありません。比喩です。)できません。そんなわけで、プログラミング言語で書いたプログラムを、機械が理解できるような信号に変換してくれるものが必要なわけです。これがインタープリタとかコンパイラなわけです。

インタープリタ方式

インタープリタは、プログラミング言語で書かれたプログラムを読み込んで、直ちにそれを機械への命令に変換して実行してくれます。ですから、インタープリタ方式では、プログラムのごく一部分であっても、ある程度まとまっていれば、その場で実行して結果を確かめることができます。簡単便利なわけです。ところが、反面、前回も触れたような行ったり来たりの構文解析をしたり、さらに数式みたいな外見の文の意味を機械の命令に翻訳したりという作業を、一行毎にしながら実行して行くため、高速の処理が苦手です。もっとも最近は機械自体の処理速度がめちゃくちゃ早くなっているので、特別高速処理が必要な部分じゃなければ、インタープリタ方式でも苦痛なほど鈍いということはないようです。そのため、簡便性が買われて、インタープリタ方式しか提供されていない言語もかなり広く愛好されるようになりました。

コンパイラ方式

これに対して、コンパイラは、それ自体はプログラムを実行してくれません。プログラミング言語で書かれたプログラムを、機械が理解できるデータに変換して、普通はそれをファイルとして保存するということをします。この生成されたファイルはいわゆるアプリケーションになっているはずなわけで、これを実行すればいいわけです。実行の段階では、もう構文解析とか機械語への翻訳は終わっているので、最適な実行速度が得られるわけです。ところが、コンパイラ方式の場合、実行するファイルはアプリケーションとしてある程度完成していないといけないわけで、足し算の部分だけ、とかを試してみることができません。そんなわけで、あらかた完成してから、デバッグと呼ばれる手順で、専用のアプリケーション(デバッガという)を用いてときどき止めながら実行してみて、長いプログラムの中にちりばめられた多くの失敗を捜す必要もでてきます。インタープリタのように書きながら断片毎にチェックできればミスのありかもわかりやすいのですが、それができない、ということです。それでも、実行速度が欲しい商用のアプリケーションはもちろん、ごく普通にアプリケーションというと、コンパイラ方式でつくられたものがほとんどです。本格的、というイメージがあるのかもしれません。

すこし補足します。上で"コンパイラというソフトウェア"といいましたが、これは、ちょっと広い範囲のものを含んでいます。まあ、この広い意味のコンパイラという言い方も、そう特殊ではないんですが。厳密にいうと、コンパイラは、プログラミング言語で書かれたプログラムを、マシン語の命令の束に変換するところまでをするソフトウェアで、その結果をファイルに落としたとしても、必ずアプリケーションになるとは限らないわけです。この後アプリケーションに至るプロセスの中で重要な働きをするソフトウェアはリンカ(linker)と呼ばれます。特に、スタティック(static)リンカですね。静的リンカ。だいたいは、これも合わせてコンパイラというわけです。
少し細かい話ですが、プログラムを書くとき、複数のファイルにわけて書くのが普通です。狭い意味でのコンパイラというのは、このプログラムファイル毎に、ひとつのマシン語ファイルを生成するのが普通です。で、自分のところで内容がわからない(定義されていない)サブルーチン、と定義されているサブルーティンとにわけて、タップというか継ぎ手みたいな記号をファイルに書き込んでおくわけです。そのあと、スタティックリンカは、その継ぎ手を調べて、定義内容と、それを必要としている部分を結びつけた上で、ひとつの実行可能ファイルにまとめてくれるわけです。実は、Mops/Forthではこの作業はないんですが、"普通の"コンパイラ型言語では、だいたいこういう仕組みになっていると思います。
ファイルを別々にわけたまま、とりあえずは実行できるようにしておいて、必要になったときに"継ぎ手"を調べて結びつける仕事をするソフトウェアは、ダイナミック(dynamic)リンカと呼ばれます。動的リンカですね。
この手の話は、高度な話ということになってるんですが、まあ、発想は単純ですよね。仕組みを作るのは難しそうですが。面倒なら、あまり考えなくていいです。

バイトコード方式

で、以上のようなものは両極で、中間にいろいろ技巧をこらしているものがあります。その中でも、今ではJavaのせいで触れないわけにはいかなくなった、バイトコード方式というのがあります。つまり、インタープリタ方式とコンパイラ方式の中間にあるような方法です。

この方式では、プログラミング言語で書かれたプログラムは、まずコンパイル(コンパイラで変換すること)される必要があります。しかし、ここで一挙に機械語に変換はしません。バイトコードと呼ばれる、人間には読めない、しかし機械で直接実行もできないデータに変換するのです。これは、大抵、ファイルとして保存されます。出来上がったファイルは、このデータのインタープリタで実行されます。このインタープリタは、"仮想機械(バーチャルマシン:virtual machine)"と呼ばれるのが普通です。

なお、バイトコードというのは厳密には、Java(他にはSmalltalkという言語でも)で使われる実行ファイルデータの呼び名(型式名)で、別に、この方式を採用するのに、バイトコードというタイプを使う必然性はありません。ので、正確には、中間コード方式とでもいう方がいいでしょう。まあ、Javaが有名なもので。

バイトコードはプログラミング言語の構文解析はもう終わっており、手早く機械語に変換できるようになっています。ですから、インタープリタ(仮想機械)で実行するときには、直接機械語を実行するとき程ではありませんが、比較的高速になります。他方、コンパイルが必要ですから、断片を手軽に試すということもできません(直接ソースを実行できるインタープリタもあるかも知れません。すみません。商品状況はよく知りません。ちょっとずつ追加コンパイルできるようになっていれば、かなり好いわけですが。)。

すると、見方によっては、インタープリタ方式ほどの手軽さもなく、コンパイラ方式よりも実行速度が遅い、いいところないじゃないか、といえなくもありません。ですが、このバイトコード方式の狙いは、皆さんご承知のように、違うところにあるのです。インタープリタ方式は、インタープリタというアプリケーションが、プログラムをその機械やOSに合わせて実行してくれるわけで、"理想的には"どの機械、どのOSでも、同じプログラムを同じように動かせるはずなわけです。ところが、あまりに実行速度が遅いのではイライラする。そこで、手早く機械語に変換できる言語をつかってインタープリタで動かせば、違う機械、違うOSでも、"理想的には"一つのプログラムが比較的高速で同じように動かせるじゃあないか、ということになるわけです。つまり、機械、というか特にOSの違いの吸収がその目的なわけです。もっとも、機械語に変換しやすいバイトコードは、人間にわかりやすいとはいえない。そこで、コンパイラをかませて、人にもわかりやすいコードをバイトコードに変換するという機構もつけたわけです。Javaは商業的にも大成功した、みたいです。

言語の特性とは言い切れない

インタープリタ方式、コンパイラ方式、中間方式、どれもプログラムの実行方式です。ときどき、インタープリタ言語、コンパイラ言語、などという分類がありますが、インタープリタでするかコンパイラでするかは、厳密にはプログラミング言語の属性ではありません。実際、C言語やC++言語というのはコンパイラ言語のチャンピオンですが、どちらもインタープリタが存在しています(過去形の方がいいのかな。あまり知られていませんね。学習用ということでしょう。)。ただ、全然関係ないかというと、そうでもありません。というのは、何度か触れたように、インタープリタでは構文解析とか、プログラミング言語を機械に適切なように置き換えるプロセスが実行されます。インタープリタ上で動かすことを予定して言語をデザインするには、この時間を最小限にするのが理想的ということになります。もちろん、人が読めるという条件を前提としてですが。他方、コンパイラ方式で動かすというなら、この変換にかかる時間は、あまりに長いのは問題ですが、それほど大きな意味を持ちません。どちらかというと、機械語の実行コードの実行効率を上げることができるような言語デザインが大切になってきます。コンパイラ方式の最大の利点は効率性ですから。つまり、プログラミング言語をデザインするときには、どちらを前提とするかで考え方が違ってくるわけで、言語の見た目も変わってくる可能性はあるわけです。もっとも、実際には、インタープリタ言語は機械のパワーに依存してしまっていて、コンパイラ言語と変わらない体裁のものがほとんどですし(似ている方がおぼえやすいし)、コンパイラ言語は、言語デザインで効率性を考えるというより、コンパイラ技術に依存してしまっているわけですが。

Mopsの方式

Mopsは、インタープリタであると同時にコンパイラでもあります。じゃあバイトコード(中間コード)方式なのかというと、そうでもありません。インタープリタ方式とコンパイラ方式の中間ではないのです。強いていえば、インタープリタのようにコード断片を試せるコンパイラ方式です。

Mopsには定義されているものと定義されていないものがあります。簡単な足し算"+"などは予め定義されています。自分で新たに定義を付け加えることもできます。既に定義されているものは、Mopsのウィンドウにenterすると実行されます。これはインタープリタの動作です。ところが、この解釈実行の段階にはコードを機械語に変換するという部分がありません。定義したときに、もうその内容は機械語に変換されて辞書(Dictionary)と呼ばれるメモリー部分に格納されてあるのです。インタープリタは、それを引っ張り出して実行するだけです。ですから、既に定義されている部分を実行している間は、機械語を単純に実行しているだけなのです。ですから、実行方式は、コンパイラ方式での実行ファイルの実行と変わりがありません。そのため、インタープリタ方式とは思えない程高速なのです。Mopsの方式は、Forth系言語の中でも、ネイティブ方式と呼ばれ、最も高速な部類に属します。これは、実質的には、もう、インタープリタ方式とはいえません。コンパイラ方式というべきでしょう。それでも、Mopsは普通のインタープリタのように、足し算一個でも、試してみることができるのです。

と、以上のように、技術的には色々あり得るわけです。Mopsの独特の方式(他にもForth系には類例はありますが)は、古いForth言語のやり方を踏襲して発展させたものです。このような、実質的にはコンパイラ方式なのに簡便にプログラミングができることを標準としている環境は、Forth系以外にはまだ存在していないようです。技術的にもとても興味深いものだと思います。






最終更新:2019年01月01日 21:29