雑記
コンパイラはそもそも何をやってくれるのか??
最終更新:
匿名ユーザー
-
view
C言語などのプログラミングにおいては、「コンパイル」という作業をすることで実行可能なバイナリを生成します。
これをしないと、いくらすばらしいコードを書いたとしても、それは単なるテキストファイルでしかありません。
では、このコンパイルを行ってくれる「コンパイラ」って、正味な話何をやってくれてるんでしょうか??
われわれプログラマがコンパイラに期待していることは、
①何よりもまず実行可能なバイナリを吐くこと
②できたら「最適化」を行ってより効率のいい処理に置き換えてくれること
でしょう。
①何よりもまず実行可能なバイナリを吐くこと
②できたら「最適化」を行ってより効率のいい処理に置き換えてくれること
でしょう。
②については、以前にも書いてあるのでここでは割愛。
①について、テキストファイルを読み込んで、それを実行可能なバイナリに変換するには、いったいどういう手順が行われているのかを、ここでは書きたいと思います。
プログラマから「コンパイルしてよ!」と指示を受けたコンパイラがまず最初にやること。それはソースファイルの読み込みですね。至極当たり前のことですが。
ソースファイルを読み込んだ後、'#'で始まるプリプロセッサ命令を処理します。
この時にインクルードファイルを展開するので、これ以降の処理にとってはヘッダファイルに宣言された共通の関数も当該ソースファイル内でローカルに宣言された関数も、見え方は同じになるというわけです。また、#defineされた定数やマクロもこの時点で展開されます。
この時にインクルードファイルを展開するので、これ以降の処理にとってはヘッダファイルに宣言された共通の関数も当該ソースファイル内でローカルに宣言された関数も、見え方は同じになるというわけです。また、#defineされた定数やマクロもこの時点で展開されます。
この処理の次に、【字句解析】と呼ばれる処理を行います。
言葉だけ聞くとなんだか小難しいわけですが、何のことは無く上で展開した後のソースを最初から1文字ずつ読み込んで処理していくだけです。
具体的には
①空白は読み飛ばす
②一定のルール(空白や確固で区切るなど)に従ってトークンとして切り出す。
③切り出したトークンがアルファベットで始まりアルファベットまたは数字で構成されていれば、それが予約語か識別子かを判別
④数字のみのトークンは数値と判定する
⑤演算子のトークンは演算子と判定する
といった処理を行います。
①空白は読み飛ばす
②一定のルール(空白や確固で区切るなど)に従ってトークンとして切り出す。
③切り出したトークンがアルファベットで始まりアルファベットまたは数字で構成されていれば、それが予約語か識別子かを判別
④数字のみのトークンは数値と判定する
⑤演算子のトークンは演算子と判定する
といった処理を行います。
if(A == (B+C))
という文があったとすれば、
まず
まず
[if] [(] [A] [==] [(] [B] [+] [C] [)] [)]
というトークンに分解されます。
そしてそれぞれのトークンに、
[if]→ 予約語 [(] → 括弧(区切り文字) [A] → 識別子 [==]→ 演算子 [(] → 括弧(区切り文字) [B] → 識別子 [+] → 演算子 [C] → 識別子 [)] → 括弧(区切り文字) [)] → 括弧(区切り文字)
などといった区分を付加します。
C言語で実装するなら区分を列挙型で定義して構造体に区分と実際のトークンへのポインタをもつような感じでしょうか?
そして次に、切り出したトークンを用いて【構文解析】を行います。
これも言葉は小難しいですが、要は一定のルールに従ってトークンとトークンの並びから構文ツリーを作成していく処理のことです。
この処理は言語に依存します。演算子の優先順位や、そもそも言語によって演算子自体会ったりなかったりなんてこともあるわけで。
上の例の場合だと、
if \ == /\ A + /\ B C
こんな感じになるのかな?
ここの処理の実装方法を書いていくと長くなるので、それについてはその気になったときに書きます。
つづいてさらに【意味解析】を行います。
上の例だと、すべては書きませんがたとえば
○「識別子B」と「識別子C」がぶら下がっているのは「演算子+」であるので、BとCの加算をおこなう必要がある
○全体が「予約語if」にぶら下がっているのでそれ以下がTRUEかFALSEで処理を分岐する必要がある
などということを判断するわけです。
○「識別子B」と「識別子C」がぶら下がっているのは「演算子+」であるので、BとCの加算をおこなう必要がある
○全体が「予約語if」にぶら下がっているのでそれ以下がTRUEかFALSEで処理を分岐する必要がある
などということを判断するわけです。
そして最後にその判断を元に、それを実現するマシン語あるいは中間コードなどを吐くわけです。
実際の商用コンパイラなどは、最適化処理などもっともっと複雑なことをやっているわけですが、大まかな流れとしてはこんな感じです。
いつもがんばってくれているコンパイラさんが、どんな働きをしているのか。それを知ることは無駄にはならないでしょう(多分)。
いつもがんばってくれているコンパイラさんが、どんな働きをしているのか。それを知ることは無駄にはならないでしょう(多分)。