【仕組み解説】コンパイラはどうやってソースコードを実行ファイルに変換しているのか — コンパイルの裏側を図解


コンパイルの流れ ソースコード int x = 10; 字句解析 Lexer トークン分解 構文解析 Parser AST生成 最適化 Optimizer 不要コード除去 コード生成 Generator 機械語出力 0110 1001 各段階の処理イメージ 字句解析 "int" "x" "=" "10" ";" 単語ごとに分割 構文解析 (AST) 代入文 x 10 最適化 dead code除去 定数畳み込み等 コード生成 MOV eax, 10 CPU命令に変換 AOTコンパイル(事前) C / C++ / Rust / Go 実行前にすべて機械語へ JITコンパイル(実行時) Java / JavaScript / C# 実行しながら動的に最適化
コンパイルの流れ(ソースコード→機械語)
ひよこ ひよこ

プログラミングで「コンパイル」ってよく聞くけど、あれって何をやってるの?

ペンギン先生 ペンギン先生

コンパイラは、人間が書いたソースコードをコンピュータが実行できる機械語に翻訳してくれるソフトウェアだよ。人間の言葉をコンピュータ語に通訳してくれる存在だね。

ひよこ ひよこ

一気に翻訳するの?それとも段階があるの?

ペンギン先生 ペンギン先生

大きく分けて4段階あるよ。字句解析→構文解析→最適化→コード生成の順番で処理されるんだ。料理に例えると、材料を切る→レシピ通りに並べる→無駄を省く→実際に調理する、みたいな流れだね。

ひよこ ひよこ

字句解析って何をするの?

ペンギン先生 ペンギン先生

ソースコードを「トークン」という最小単位に分解する作業だよ。たとえば int x = 10; というコードなら、「int(型)」「x(変数名)」「=(代入)」「10(数値)」「;(区切り)」の5つのトークンに分けるんだ。文章を単語ごとに区切るイメージだね。

ひよこ ひよこ

なるほど!じゃあ構文解析は何をするの?

ペンギン先生 ペンギン先生

トークンの並び順が文法的に正しいかチェックして、AST(抽象構文木)というツリー構造を作るよ。「変数xに10を代入する」という意味の木ができるんだ。日本語でいうと、単語を主語・述語・目的語に分類して文の構造を理解する感じだね。

ひよこ ひよこ

ASTって聞いたことある!そのあとの「最適化」って何をするの?

ペンギン先生 ペンギン先生

プログラムの意味を変えずに、もっと速く・もっと小さくなるように書き換える工程だよ。たとえば、絶対に実行されない if 文を削除したり、ループの中で毎回同じ計算をしている部分をループの外に出したりするんだ。プロの料理人が無駄な動きを省いて効率よく調理するのと同じだね。

ひよこ ひよこ

賢いんだね!最後のコード生成は?

ペンギン先生 ペンギン先生

最適化されたASTから、CPUが直接実行できる機械語(0と1の列)を生成する工程だよ。x86ARMなど、ターゲットのCPUアーキテクチャに合わせた命令を出力するんだ。ここでようやくコンピュータが理解できる形になるんだね。

ひよこ ひよこ

JavaとかJavaScriptは「JITコンパイル」って聞くけど、普通のコンパイルと何が違うの?

ペンギン先生 ペンギン先生

通常のコンパイラは実行前にすべて機械語に変換するけど、JIT(Just-In-Time)コンパイラは実行中にコンパイルするんだ。Javaはまずバイトコードという中間形式にコンパイルして、実行時にJVMがJITで機械語に変換する。JavaScriptもV8エンジンがJITコンパイルしてるよ。実行しながら「この関数よく呼ばれるな」と判断して重点的に最適化できるのがJITの強みだね。

ひよこ ひよこ

コンパイルエラーとランタイムエラーって何が違うの?

ペンギン先生 ペンギン先生

コンパイルエラーは翻訳の段階で見つかるミスで、文法ミスや型の不一致なんかが該当するよ。ランタイムエラーは実行してみないとわからないエラーで、ゼロ除算やnull参照が代表例だね。コンパイルエラーは「レシピの書き方がおかしい」、ランタイムエラーは「レシピ通りに作ったけど材料が足りなかった」みたいな違いだよ。

ひよこ ひよこ

コンパイルエラーで止まってくれる方がありがたいんだね!

ペンギン先生 ペンギン先生

その通り!だからTypeScriptのように型チェックを厳しくする言語が人気なんだ。コンパイル時にできるだけ多くのバグを見つけてくれれば、本番環境でのクラッシュを減らせるからね。ちなみに現代のコンパイラは、LLVMのように「フロントエンド(言語ごとの解析)」と「バックエンドCPU向けコード生成)」を分離する設計が主流で、RustSwiftZigなど多くの言語がLLVMバックエンドを共有しているんだよ。

ひよこ ひよこ

いろんな言語が同じエンジンを使ってるなんて面白いね!

ペンギン先生 ペンギン先生

そうだね。LLVMのおかげで新しい言語を作るハードルが大きく下がったんだ。言語設計者はフロントエンド(構文解析まで)を作るだけで、最適化や各CPUへのコード生成はLLVMに任せられる。コンパイラの世界も分業と再利用が進んでいて、まさにソフトウェアエンジニアリングそのものだね。