人が書いたソースコードは、そのままでは動きません。 機械が直接実行できる命令の並び(機械語)に翻訳する必要があり、 その翻訳をするのがコンパイラです。 翻訳は一気に行われるのではなく、4つの段階を順に通ります。 試験で問われるのは、どの段階で何をしていて、どの誤りがどこで見つかるかです。
出典:基本情報技術者試験 シラバス Ver9.2 大分類1 基礎理論/中分類1 基礎理論/3. 情報に関する理論
(9)コンパイラ理論
コンパイラの役割,コンパイルの過程,字句解析,構文解析,意味解析,コード生成,最適化の基本的な考え方を理解する。
(10)プログラム言語論
適用分野などの違いによって,各種のプログラム言語が存在することを理解する。また,代表的なプログラム言語の概念,構文と意味のあらまし,適用分野を理解する。
コンパイルの4つの段階
ソースコードが目的プログラムになるまで
1. 字句解析 … 文字の並びを、意味のある最小のまとまり(字句)に切り分ける
2. 構文解析 … 切り分けた字句が文法どおりに並んでいるかを調べ、構文木を作る
3. 意味解析 … 文法は合っていても内容として通るかを調べる
4. コード生成 … 機械語の命令の列に書き出す
※ 途中で中間言語という共通の形を経ることが多く、最適化はそのあたりで行う。最適化は5つ目の段階ではなく、結果を変えずに直す別の作業
字句解析 ── 文字の並びを切り分ける
x = a + b * 2 を、意味のある最小のまとまりに切る x 名前(識別子) = 記号(演算子) a 名前(識別子) + 記号(演算子) b 名前(識別子) * 記号(演算子) 2 数(定数) 切り出した1つずつを「字句(トークン)」という
空白や改行はここで捨てられます。どこまでが1つの名前かを見分ける作業なので、1-8 の有限オートマトンがそのまま使えます。
ここで見ているのは並べ方ではなく、区切り方です。 だから x = + + a のように順番がおかしい並びでも、字句解析は通ります。 通らないのは、どの字句にもならない文字があるときです。 たとえば x = a ? b の ? が、その言語の記号として決められていなければ、 切り出せずにここで止まります。
構文解析 ── 文法どおりに並んでいるか
x = a + b * 2 の構文木
=
/ \
x +
/ \
a *
/ \
b 2
枝の先(葉)が値、枝の分かれ目が演算
いちばん上の = は「等しい」ではなく「左へ入れる」(代入)
計算は下から順に行われる
掛け算が足し算より下にあるので、先に計算されます。並びの優先順位が、木の深さとして残る形です。
文法は 1-8 の BNF で書かれています。 並びが文法に合わなければ、ここで誤りとして止まります。できた木を構文木といいます。 さきほどの x = + + a が止まるのは、この段階です。
意味解析 ── 内容として通るか
ここで出てくる言葉を2つ、先に決めておきます。 型は、その名前が数なのか文字の並びなのかという種類のことです。 宣言は、使う前に「この名前はこの種類で使います」と書いておくことです。
意味解析で見つかる誤りの例
型が合わない … 文字の並びと数を足そうとしている(足せないと決めている言語の場合)
宣言されていない … どこにも宣言していない名前 c を、いきなり使っている
形が正しいことと、内容が通ることは別です。 並び方としては正しくても、中身として成り立たないものは、ここで止まります。
練習
型を厳密に検査する言語で、a は文字の並び、b は数として宣言されています。 ここで x = a + b と書きました。この誤りは、どの段階で見つかりますか。
コード生成と、実行を速くする最適化
検査を通ると、機械語の命令の列に書き出すコード生成に進みます。 その途中で、いったん中間言語という形にすることがあります。 機械語そのものではなく、どの機種向けにも書き出しやすい共通の形です。 中間言語をはさむと、前半の解析を機種ごとに作り直さずに済みます。
書き出されたものが目的プログラムです。 ただし、これだけでは動きません。ほかの部品と結合して、はじめて実行できる形になります (結合の話は第5章で扱います)。
先に、ひとつ予想してみましょう
コンパイラは、プログラムを速くするための書き換えをすることがあります。 次のうち、その書き換えとして正しいものはどれですか。
同じ結果のまま、実行を速くする
直す前
1000回くり返す { y = a * b ; 合計 = 合計 + y }
直した後
y = a * b ; 1000回くり返す { 合計 = 合計 + y }
a も b も、くり返しの中で変わらない
くり返しのたびに同じ値になる計算を、外へ出しています。足し算は 1000 回のままですが、掛け算は 1000 回から 1 回になりました。
このように、結果を変えずに実行を速くしたり、大きさを小さくしたりする作業が最適化です。 使われない計算を消す、くり返しの外へ出す(ループ不変式の移動)、といった直し方があります。
練習
a と b はどちらも数として宣言されています。 ここで x = a / b と書きました。b に 0 が入ったときだけ、0 で割ることになります。 この誤りは、どの段階で見つかりますか。
まとめて訳すか、その場で訳すか
2つの翻訳のしかた
コンパイラ … 実行の前に全部訳して目的プログラムを作る。実行は速いが、直すたびに訳し直す
インタプリタ … 1行ずつ訳しながら実行する。すぐ試せるが、実行のたびに訳すぶん遅い
プログラム言語の種類
言語がたくさんあるのは、向いている分野が違うからです。 「何を書くか」の考え方によって、大きく4つに分けられます。
何を書く言語か
手続型言語 … やる手順を書く。上から順に実行され、変数の値を書き換えながら進む(C。機器の組み込みや基本ソフト)
オブジェクト指向言語 … 部品を書く。データとその扱い方をひとまとめにし、組み合わせて作る(Java。大きな業務システム)
関数型言語 … 関数の組み合わせを書く。関数とは、まとまった計算に名前を付けたもの。途中の値を書き換えないので、同じ入力なら必ず同じ結果になる(Lisp、Haskell。計算の正しさが要る処理)
論理型言語 … 成り立つ事実と規則を書く。条件に合うものはコンパイラやインタプリタが探す(Prolog。推論や知識の処理)
手続型とオブジェクト指向は「どうやるか」、関数型と論理型は「何が成り立つか」を書きます。 前の2つを命令的、後ろの2つを宣言的といいます。 なお、この分け方は重なることがあります。同じ言語で手続型にもオブジェクト指向にも書けるものは珍しくありません。 1-8 の言葉でいえば、論理型言語は述語論理の考え方を土台にした言語です。
練習
大きなシステムを分担して作ります。データとその扱い方をひとまとめにした部品を用意しておき、 あとから部品を差し替えたり足したりして機能を広げたい。どの考え方の言語が向いていますか。
まとめ
この回で持ち帰ること
1. コンパイルは字句解析 → 構文解析 → 意味解析 → コード生成の順。中間言語を経ることが多く、最適化はそのあたりの別作業
2. 字句解析は切り分け(切り出せない文字で止まる)、構文解析は並び、意味解析は内容(型・宣言)
3. 実行してみるまで分からない誤りもある。コンパイルが通っても、正しく動くとは限らない
4. 最適化は結果を変えずに速く・小さくする。書き出されるのが目的プログラムで、結合して実行できる形になる
5. 言語は命令的(手続型・オブジェクト指向)と宣言的(関数型・論理型)に分かれる
次に読む
この記事で引用した資料
いずれも独立行政法人情報処理推進機構(IPA)が公表したものです。IPAは公表済みの試験問題について、教育目的での使用に許諾および使用料を不要としていますが、著作権は放棄していません。本記事では問題文を改変せずに引用しています。
本文の例(x = a + b * 2 の解析、誤りの見つかる段階、くり返しの外へ出す書き換え)と練習問題は、すべて当サイトが説明のために作ったものです。

