Compiler Construction Lecture 12/20

Menu Menu


先週の復習

先週は、register を使った compiler の作成方法を勉強した。最適化を考えなければ、コード生成自身はstack versionとほとんど変わらない。これは、registerの番号の上にstackのコードを部分計算することに相当する。先週解いた問題は、このような方法でどのようなコードが生成されるかを問うものだった。もちろん、これよりも、賢いコード生成をおこなうことは簡単だが、それを問題の答えとするならば、同時にコード生成のアルゴリズムを示さなければ完結した解答とはいえない。


Micro-C の全体構成

全体構成をつかみ、Cの文法を思い出しながら、個々の関数を読んでいくことにより、実際的なcompilerの詳細を理解して欲しい。自分で、すべてを理解しなくては、どんなプログラムでも完成させることはできない。あてずっぽうにコードを書いても、それは絶対に動くことはないし、それは、他のコードに影響を与え、正しい部分まで汚染してしまう。バグは避けられないものではあるが、自分で書いたコードを完全に理解していれば、どのようなバグにも対処できる。

Micor-C の全体構成


分割コンパイルとモジュールまたはオブジェクト

複雑なプログラムになると、プログラムを一つのファイルにまとめることもできなくなる。また、そうしなければ、複数の人間でプログラムを書くということもできない。そこで、プログラムを分割してcompileするということが必要になる。

分割した時に、分割した部分のどこを外に見せて、どこを隠すかというのが問題になる。この場合の単位は module と呼ばれる。すべてを見せてしまっては、分割した意味がないともいえる。しかし、見る方が悪いのであって、すべてはdefaultで見えている方が良いという考え方も存在する。実際には、見せるためには、分割したもの同士の情報のやり取りが必要なので、import, export や、public, privateなどのkeywordで情報のやり取りを制御するようにすることが多い。Cでは、extern と static がそれに相当する。C++では、publicとprivateを使う。

このような情報の制御と、ファイルの分割は本来は独立な問題である。しかし、Cではファイルの分割でしか module を定義できない。C++の場合には class がその役割を果たしている。




Symbol Table

Tokenizerでは、記号の切り出しとkeywordの切り出しをおこなう。同時に、名前の登録をおこなう必要がある。実際、Tokenizerが、普通の名前、(例えば英字で始まる英数字の列などだが)に出会ったとしよう。それは、名前(name)、つまり、予約語(reserved word)か、変数名か関数名である。Micor-C では、getsym() という関数がTokenizerである。

これらは compiler 内部の表に登録されなければならない。この表を Symbol Tableという。Interpreter の場合でもSymbol Tableは必要である。Compilerが出力したコードでは、Symbol Tableは不要である。しかし、分割compile (Separate compilation)の場合は、相互のSymbol Tableを接続する必要があるので、Symbol Table そのものも出力しなくてはならない。この問題は、異なるcomputer上で分散計算を行う場合にも出て来る。

名前には様々な属性がある。例えば、ある名前はlocalであり、ある関数や、関数の一部でしか有効でない。ある名前で指し示される(実体/objectゥ)ものは、名前で呼ばれなくなった後も存在し続ける場合がある。あるいは、そのようなものを別な名前で呼びたい時もある。このような属性には以下のようなものがある。

例えば、Cだったら、scopeとextentには以下のようなものがある。compilerにとって問題なのは変数の有効範囲であり、大域名(global)と局所名(local)が衝突した場合には局所名を優先する必要があり、局所名が有効でなくなれば、その名前を再利用する必要がある。

名前には、その名前が指すもの(object)の型がある。一つの名前には一つの型がある言語(Cはそうだ)もあるが、一つの名前を複数の型に使うことができるもの(Perlなど)もある。前者の場合は、名前を検索することにより型が分かる。しかし、後者の場合には、型は名前とは別に指定する必要がある。後者の方がどちらかといえば優れているようである。Cには以下のような型がある。


変数の使い方

分割コンパイルする場合は、Cでは以下のようにすることが多い。

ただし、分割が進むと、大域関数とかを、すべてのファイルが使うとは限らない。その時には、.c ファイルに対応した、.h を用意して、使うものだけど、#include するということになる。この時に、#include の順序に依存したり、2度 include したりしないように注意する必要がある。

定数の設定には、#define ではなく、enum を使う場合もある。この方が、debugger が定数の表示を知ることができるので、debug しやすくなる。


宿題

今週の宿題はありません。

アンケート


Shinji KONO / Mon Dec 20 11:31:15 1999