- 技術 💻
コンパイルとは|意味・インタプリタとの違いを6言語の実行で解説
結論
コンパイルとは、人が書いたプログラムを、コンピューターが実行できる形にまとめて変換することです。変換するソフトをコンパイラと呼びます。
この記事でわかること
- 6言語で同じプログラムを変換してできたファイルと中身
- インタプリタ・ビルド・JITとの違いの表と速さの実測
- コンパイルエラーと実行時エラーの原文13件
コンピューターの CPU(計算をする部品)が直接読めるのは、0 と 1 の並びでできた「機械語」だけです。コンパイラは、人が読み書きしやすい言葉で書いたソースコードを、実行する前にまとめて機械語などに変換します。
flowchart TD
A[ソースコード<br>人が書いた sumsq.c] --> B[コンパイラ<br>gcc など]
B --> C[実行ファイル<br>機械語]
C --> D[CPU が実行<br>sum=385]
A -.->|インタプリタ型は<br>変換しながら実行| E[インタプリタ<br>python など]
E -.-> D
急いでいる人向けに、よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| コンパイルとは | ソースコードを実行できる形にまとめて変換すること | コンパイルとは |
| コンパイラとは | コンパイルをするソフト(gcc・javac など) | コンパイルとは |
| インタプリタとの違い | 先にまとめて訳すか、実行しながら訳すか | インタプリタとの違い |
| ビルドとの違い | ビルドはコンパイル+リンクなど全部の工程 | ビルドとの違い |
| JIT とは | 実行中に、よく使う部分を機械語に変換する | JIT |
| Python は? | 自動でバイトコードに変換してから実行 | 6言語で試す |
| コンパイルエラーとは | 変換の段階で見つかる誤り。実行ファイルができない | エラーの違い |
| 速さの差 | 同じ計算で C・Go は約0.14秒、Python は約3.5秒 | JIT |
確認環境:Ubuntu 24.04.4 LTS(Intel Xeon 2.10GHz・2コア)。同じ計算をするプログラムを6つの言語で書き、gcc 13.3.0(GNU Binutils 2.42 の objdump・nm、GNU Make 4.3)、OpenJDK 21.0.10 の javac・java・javap、Python 3.11.15 の py_compile・dis、Node.js 22.22.2(V8 12.4)、npm で入れた TypeScript 7.0.2 の tsc、Go 1.24.7 の go build で実際にコンパイル・実行し、できたファイルの大きさ・先頭のバイト(hexdump)・file コマンドの判定・中身の命令・エラーの原文・実行時間を載せています。コンパイラの段階・オプションの意味は GCC・Oracle(javac・java)・Python・V8・TypeScript・Go・Rust・Microsoft Learn の公式ドキュメント、言葉の意味は MDN・IT用語辞典 e-Words・Weblio 英和辞書で確かめました(確認日 2026年10月23日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「コンパイルとは|意味・インタプリタとの違いを6言語の実行で解説」
コンパイルとは(意味・読み方・たとえ)
この節では、コンパイルという言葉の意味と、何を何に変える作業なのかが分かります。
コンパイル(compile)は、プログラミング言語で書かれたソースコードを、コンピューターが実行できる形式に変換することです。 読み方は「コンパイル」、英語の発音は「コンパイル(kəmpáɪl)」に近い音です。変換をするソフトウェアをコンパイラ(compiler)と呼びます。
MDN は「指定された言語で書かれたコンピュータープログラムを、別の形式や言語の一連の命令に変換する作業」と説明しています。IT用語辞典 e-Words は、ソースコードを CPU が実行できるオブジェクトコード(ネイティブコード)に変換すること、としています。
英語の compile は、もともと「資料を集めて編集する・編纂する」という意味の言葉です(Weblio 英和辞書)。バラバラに書かれた命令を集めて、1つの実行できるプログラムに組み上げる、という意味で使われるようになりました。
| 言葉 | 意味 | 例 |
|---|---|---|
| ソースコード | 人が書いたプログラムの文章 | sumsq.c・SumSq.java |
| コンパイラ | コンパイルをするソフト | gcc・clang・javac・tsc・go |
| 機械語 | CPU が直接実行できる 0 と 1 の命令 | 0f af c0(かけ算の命令) |
| オブジェクトファイル | 機械語にしたが、まだ実行できない部品 | sumsq.o |
| 実行ファイル | そのまま起動できる完成品 | Linux の sumsq・Windows の .exe |
| バイトコード | 仮想マシン向けの中間の命令 | Java の .class・Python の .pyc |
たとえ:翻訳家と通訳
1コンパイラは翻訳家
本を1冊まるごと訳してから渡す。訳すのに時間はかかるが、読む人(CPU)はすぐ速く読める。
2インタプリタは通訳
話を聞きながらその場で訳す。準備なしですぐ始められるが、毎回訳す手間がかかる。
3JIT は慣れた通訳
何度も出てくる言い回しは、訳し方を覚えておいて2回目から速く訳す。Java・JavaScript が使う。
なぜコンパイルが必要なのか(CPU は機械語しか読めない)
この節では、人が書いたプログラムをそのまま CPU に渡せない理由が、実際の機械語で分かります。
CPU が実行できるのは、CPU の種類ごとに決まった機械語の命令だけです。 total += i * i; という1行は、x86-64 の CPU では次のような機械語になりました。左の16進数(00〜ff の2桁で1バイト)が CPU が読む本物の命令で、右はそれを人が読めるように書いたアセンブリ言語です。
1b: 8b 45 fc mov -0x4(%rbp),%eax ← i を読み出す
1e: 0f af c0 imul %eax,%eax ← i × i
21: 01 45 f8 add %eax,-0x8(%rbp) ← total に足す
24: 83 45 fc 01 addl $0x1,-0x4(%rbp) ← i を 1 増やす
(gcc で作った sumsq.o を objdump -d で表示。← の説明は追記)
人がこの16進数で直接プログラムを書くのはたいへんです。そこで、人に分かりやすい言語で書き、コンパイラに機械語へ変換させます。
| 言語の種類 | 例 | 人の読みやすさ | CPU との距離 |
|---|---|---|---|
| 高水準言語 | C・Java・Python・Go | 読みやすい | 遠い(変換が要る) |
| アセンブリ言語 | imul %eax,%eax |
命令ごとに1行 | 機械語と1対1に近い |
| 機械語 | 0f af c0 |
読めない | CPU がそのまま実行 |
コンパイルには、ほかにも次の役目があります。
- 誤りを先に見つける:文法の誤り・型の誤りを、実行する前に知らせる(後の節)
- 速くする(最適化):同じ意味でより速い命令に置きかえる。gcc の
-O2は、答えが決まっている計算を前もって済ませた(後の節) - 配れる形にする:実行ファイルを渡せば、相手はコンパイラが無くても実行できる。Rust の公式ドキュメント(The Rust Programming Language)も、事前にコンパイルする言語の利点としてこの点を挙げています
コンパイラとインタプリタの違い
この節では、コンパイラ型とインタプリタ型の違いと、言語ごとにどちらの方式なのかが分かります。
コンパイラ型(事前にまとめて変換)
実行する前に全体を機械語に変換する
実行は速い。変換の時間が別にかかる
文法の誤りは実行前にまとめて分かる
C・C++・Go・Rust など
インタプリタ型(実行しながら変換)
ソースコードを渡せばすぐに動く
命令ごとに解釈するので、実行は遅め
通らなかった行の誤りは見つからない
Python・Ruby・PHP など
| 比べる点 | コンパイラ型 | インタプリタ型 |
|---|---|---|
| 変換のタイミング | 実行の前にまとめて | 実行しながら少しずつ |
| できるもの | 実行ファイル(機械語) | 残らない(内部で中間コードに) |
| 実行の速さ | 速い(この記事の計算で約0.14秒) | 遅め(Python で約3.5秒) |
| 動かすまでの手順 | コンパイル → 実行の2段階 | 1つのコマンドで実行 |
| 実行する PC に要るもの | 実行ファイルだけ | 言語の処理系(python など) |
| 誤りが見つかる時 | コンパイルのとき | その行を実行したとき |
今の言語の多くは、どちらか一方だけではありません。 Python の公式の用語集は、Python をインタプリタ言語としながら「バイトコードコンパイラがあるので区別はあいまい」と書いています。Java は javac でバイトコードにコンパイルし、実行中は JVM がインタプリタと JIT コンパイラを使い分けます。
言語ごとの方式の一覧
| 言語 | 主な方式 | コンパイルの命令 | できるもの |
|---|---|---|---|
| C・C++ | 事前に機械語へ | gcc・g++・clang |
実行ファイル |
| Go | 事前に機械語へ | go build |
1つの実行ファイル |
| Rust | 事前に機械語へ | rustc・cargo build |
実行ファイル |
| Java | バイトコード+JIT | javac |
.class |
| C# | 中間言語(CIL)+JIT | dotnet build |
.dll |
| Python | 自動でバイトコード | 不要(自動) | .pyc(キャッシュ) |
| JavaScript | エンジンの中でバイトコード+JIT | 不要(自動) | 残らない |
| TypeScript | JavaScript へ変換 | tsc |
.js |
C# は Microsoft Learn の「マネージド実行プロセス」で、コンパイラがソースコードを CPU に依存しない共通中間言語(CIL)に変換し、実行前に JIT コンパイラが CPU 固有のコードに変換すると説明されています。
コンパイルの流れ(gccの4段階を実物で見る)
この節では、C 言語のコンパイルが内部でどんな段階を踏むのかを、途中のファイルを1つずつ取り出して確かめられます。
GCC の公式ドキュメントは、コンパイルは「プリプロセス・コンパイル本体・アセンブル・リンク」の最大4段階を、いつもこの順で行うと説明しています。 普段は gcc sumsq.c -o sumsq の1回で4段階が続けて動きますが、オプションで各段階の後に止められます。
使ったプログラムです(1〜10 の2乗の合計を計算して表示)。この記事では、同じ内容をほかの5つの言語でも書いて比べます。
#include <stdio.h>
int sum_squares(int n) {
int total = 0;
for (int i = 1; i <= n; i++) {
total += i * i;
}
return total;
}
int main(void) {
printf("sum=%d\n", sum_squares(10));
return 0;
}
gcc -E sumsq.c -o sumsq.i # 1. プリプロセスだけ
gcc -S sumsq.c -o sumsq.s # 2. アセンブリ言語まで
gcc -c sumsq.c -o sumsq.o # 3. 機械語の部品(オブジェクトファイル)まで
gcc sumsq.o -o sumsq # 4. リンクして実行ファイルに
./sumsq
sum=385
flowchart TD
A[sumsq.c<br>224バイト・14行] -->|1 プリプロセス -E| B[sumsq.i<br>827行]
B -->|2 コンパイル -S| C[sumsq.s<br>アセンブリ 80行]
C -->|3 アセンブル -c| D[sumsq.o<br>機械語の部品]
D -->|4 リンク| E[sumsq<br>実行ファイル 16,000バイト]
プリプロセス(-E)
#include のファイルを貼り込み、#define を置きかえる。stdio.h が展開され、14行が827行(21,456バイト)になった。
コンパイル本体(-S)
文法を調べ、意味を確かめ、最適化して、アセンブリ言語に変換する。80行(1,245バイト)になった。
アセンブル(-c)
アセンブリ言語を機械語にする。1,688バイトのオブジェクトファイル。printf の場所はまだ決まっていない。
リンク
オブジェクトファイルと、printf などのライブラリをつなぎ、起動できる実行ファイルにする。16,000バイト。
2. コンパイル本体の出力(アセンブリ言語)
sumsq.s の sum_squares の部分です。ループが「比べる(cmpl)→ 条件で戻る(jle)」の命令になっています。
sum_squares:
endbr64
pushq %rbp
movq %rsp, %rbp
movl %edi, -20(%rbp)
movl $0, -8(%rbp)
movl $1, -4(%rbp)
jmp .L2
.L3:
movl -4(%rbp), %eax
imull %eax, %eax
addl %eax, -8(%rbp)
addl $1, -4(%rbp)
.L2:
movl -4(%rbp), %eax
cmpl -20(%rbp), %eax
jle .L3
movl -8(%rbp), %eax
popq %rbp
ret
(.cfi_ で始まるデバッグ用の行は省略)
3. オブジェクトファイルと 4. リンク
file コマンドで種類を見ると、.o は「relocatable(まだ場所が決まっていない部品)」、リンク後は「executable(実行ファイル)」と判定されました。
file sumsq.o sumsq
nm sumsq.o
sumsq.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
sumsq: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=dc76cc2e..., for GNU/Linux 3.2.0, not stripped
0000000000000035 T main
U printf
0000000000000000 T sum_squares
nm の U printf は「printf を使うが、このファイルの中には無い(Undefined)」という印です。 リンクの段階で、C の標準ライブラリにある printf とつながります。実行ファイルの先頭4バイトは 7f 45 4c 46(.ELF)で、Linux の実行ファイルの形式(ELF)を表す印です。
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
C 言語の環境構築・ポインタ・メモリの扱いまでは、関連記事「C言語とは|入門から環境構築・ポインタ・mallocまで実行例で解説」で扱っています。
6言語で同じプログラムをコンパイルしてみる
この節では、C・Java・Python・JavaScript・Go・TypeScript で同じ計算をしたときに、何ができて、中身がどうなっているのかを比べられます。
同じ「1〜10 の2乗の合計」でも、言語によってできるファイルの種類と大きさがまったく違いました。 6つとも sum=385 と表示されることを確かめています。
| 言語 | コンパイルの命令 | できたもの | 大きさ | 中身 |
|---|---|---|---|---|
| C | gcc sumsq.c -o sumsq |
sumsq |
16,000 B | x86-64 の機械語(ELF) |
| Java | javac SumSq.java |
SumSq.class |
979 B | JVM のバイトコード |
| Python | python3 -m py_compile sumsq.py |
__pycache__/sumsq.cpython-311.pyc |
467 B | Python のバイトコード |
| JavaScript | 不要(node sumsq.js) |
何も残らない | ― | V8 の中でバイトコード |
| TypeScript | tsc sumsq.ts |
sumsq.js |
181 B | JavaScript |
| Go | go build -o sumsq sumsq.go |
sumsq |
2,225,764 B | x86-64 の機械語(静的リンク) |
ファイルの先頭の数バイトには、種類を表す決まった印(マジックナンバー)が入っていました。
| ファイル | 先頭のバイト | 意味 |
|---|---|---|
| C・Go の実行ファイル | 7f 45 4c 46 |
.ELF(Linux の実行ファイル) |
Java の .class |
ca fe ba be 00 00 00 41 |
CAFEBABE と版 65(Java 21) |
Python の .pyc |
a7 0d 0d 0a |
Python 3.11 のバイトコードの印 |
| Go の WebAssembly | 00 61 73 6d |
.asm(WebAssembly) |
Java:javac でバイトコード(.class)にする
javac SumSq.java
java SumSq
file SumSq.class
javap -c SumSq
sum=385
SumSq.class: compiled Java class data, version 65.0
...
static int sumSquares(int);
Code:
0: iconst_0
1: istore_1
2: iconst_1
3: istore_2
4: iload_2
5: iload_0
6: if_icmpgt 21
9: iload_1
10: iload_2
11: iload_2
12: imul
13: iadd
14: istore_1
15: iinc 2, 1
18: goto 4
21: iload_1
22: ireturn
javac が作るのは CPU の機械語ではなく、JVM(Java 仮想マシン)が読むバイトコードです。 Oracle の javac のドキュメントは、javac はソースファイルを読み、Java 仮想マシンで動くクラスファイルにコンパイルする、と説明しています。imul(かけ算)・iadd(足し算)のように、命令の形は機械語に似ていますが、どの CPU でも同じ .class が動きます。JVM が実行時に、その CPU の機械語に変えます(JIT の節)。
Java 11 からは、java SumSq.java のように .java を直接渡しても動きます。Java のドキュメントでは「ソースファイルモード」と呼び、メモリの中でコンパイルしてから実行します。実際に .class ファイルは作られませんでした。
$ time javac SumSq.java → 0.808秒(.class ができる)
$ time java SumSq → 0.055秒
$ time java SumSq.java → 0.686秒(.class はできない。毎回コンパイルする)
Java のコンパイルエラーの種類と直し方は、関連記事「Javaコンパイルエラー一覧|cannot find symbol他20種と対処法」で扱っています。
Python:実行するたびに自動でバイトコードにしている
python3 sumsq.py # 実行しても __pycache__ はできない
python3 -c "import sumsq" # import されると __pycache__ にキャッシュされる
python3 -m py_compile sumsq.py # 明示的にコンパイルする
python3 -m dis sumsq.py # バイトコードを表示
Disassembly of <code object sum_squares at 0x7f203d1fb830, file "sumsq.py", line 1>:
1 0 RESUME 0
2 2 LOAD_CONST 1 (0)
4 STORE_FAST 1 (total)
3 6 LOAD_GLOBAL 1 (NULL + range)
18 LOAD_CONST 2 (1)
20 LOAD_FAST 0 (n)
22 LOAD_CONST 2 (1)
24 BINARY_OP 0 (+)
28 PRECALL 2
32 CALL 2
42 GET_ITER
>> 44 FOR_ITER 10 (to 66)
46 STORE_FAST 2 (i)
4 48 LOAD_FAST 1 (total)
50 LOAD_FAST 2 (i)
52 LOAD_FAST 2 (i)
54 BINARY_OP 5 (*)
58 BINARY_OP 13 (+=)
62 STORE_FAST 1 (total)
64 JUMP_BACKWARD 11 (to 44)
5 >> 66 LOAD_FAST 1 (total)
68 RETURN_VALUE
Python も、実行の前にソースコード全体をバイトコードにコンパイルしています。 Python の公式の用語集は、Python のソースコードはバイトコードにコンパイルされ、.pyc ファイルにキャッシュされるので、同じファイルの2回目の実行が速くなる、と説明しています。手元では、python3 sumsq.py で直接実行したファイルは .pyc が作られず、import したときに __pycache__/sumsq.cpython-311.pyc が作られました。
.pyc の先頭16バイトは「Python の版の印・フラグ・元の .py の更新時刻・元の .py の大きさ」でした。最後の4バイト 89 00 00 00 は 137(sumsq.py の大きさ)です。
00000000 a7 0d 0d 0a 00 00 00 00 14 14 db 6a 89 00 00 00 |...........j....|
用語集は、バイトコードは Python の版ごとに変わるので、別の版の間で動くことは期待できない、とも書いています。ファイル名に cpython-311 と版が入っているのはそのためです。
JavaScript:エンジンの中でバイトコードにしてから動く
node --print-bytecode --print-bytecode-filter=sumSquares sumsq.js
[generated bytecode for function: sumSquares (0x003df3d1aec1 <SharedFunctionInfo sumSquares>)]
Bytecode length: 36
39 S> 0x88447701eb0 @ 0 : 0c LdaZero
0x88447701eb1 @ 1 : c9 Star0
57 S> 0x88447701eb2 @ 2 : 0d 01 LdaSmi [1]
0x88447701eb4 @ 4 : c8 Star1
62 S> 0x88447701eb5 @ 5 : 0b 03 Ldar a0
62 E> 0x88447701eb7 @ 7 : 73 f8 00 TestLessThanOrEqual r1, [0]
0x88447701eba @ 10 : 9e 17 JumpIfFalse [23] (0x88447701ed1 @ 33)
79 S> 0x88447701ebc @ 12 : 0b f8 Ldar r1
90 E> 0x88447701ebe @ 14 : 3d f8 02 Mul r1, [2]
0x88447701ec1 @ 17 : 3b f9 01 Add r0, [1]
...
sum=385
JavaScript は、ファイルを残さずに Node.js(V8 エンジン)の中でバイトコードに変換してから実行しています。 V8 の公式ドキュメントは、V8 には Ignition という高速なレジスタ型のインタプリタがあると説明しています。上の Mul(かけ算)・Add(足し算)が Ignition のバイトコードです。よく使われる関数は、さらに TurboFan(V8 の最適化コンパイラ)が機械語にします(JIT の節)。
TypeScript:tsc で JavaScript に変換する(トランスパイル)
npm install -D typescript # 7.0.2 が入った
npx tsc sumsq.ts
cat sumsq.js
"use strict";
function sumSquares(n) {
let total = 0;
for (let i = 1; i <= n; i++) {
total += i * i;
}
return total;
}
console.log(`sum=${sumSquares(10)}`);
tsc は TypeScript を機械語ではなく JavaScript に変換し、型の注釈(: number)は消えます。 このように、ある言語を同じくらいの水準の別の言語に変換することをトランスパイルと呼ぶこともあります。TypeScript の公式ドキュメントでは tsc を「the TypeScript compiler」と呼び、型の注釈は出力ですべて消える(実行時の動きを変えない)と説明しています。
TypeScript 7.0 は2026年7月8日に公開され、公式ブログによると、コンパイラそのものが Go で書き直されました(全体のビルドで8〜12倍速い、としています)。手元で入れた tsc 7.0.2 の本体も、Go でコンパイルされた機械語のファイルでした。コンパイラ自身も、別のコンパイラで作られたプログラムです。
$ file node_modules/@typescript/typescript-linux-x64/lib/tsc
...tsc: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=..., stripped
7.0 では、6.0 で非推奨だった --target es5 などが使えなくなりました。実際に指定すると次のエラーになりました。
error TS5108: Option 'target=ES5' has been removed. Please remove it from your configuration.
インストールから tsc の設定までは、関連記事「TypeScript入門|インストールからtscで実行するまでの始め方」で扱っています。
Go:go build で1つの実行ファイルにする
go build -o sumsq sumsq.go
./sumsq
file sumsq
sum=385
sumsq: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=c967d0ed..., with debug_info, not stripped
Go は必要なライブラリをすべて取り込んだ(静的リンク)1つの実行ファイルを作ります。 そのため C の16,000バイトより大きく、2,225,764バイトでした。デバッグ情報を外す -ldflags="-s -w" を付けると 1,446,072 バイトになりました。go run sumsq.go はコンパイルと実行を1回で行う命令で、Go の公式ドキュメントでは「名前を付けた main パッケージをコンパイルして実行する」と説明されています。
-gcflags=-m を付けると、コンパイラが何を最適化したのかが表示されます。
./sumsq.go:5:6: can inline sumSquares
./sumsq.go:14:35: inlining call to sumSquares
関数 sumSquares は、呼び出し元の main に中身を埋め込む「インライン化」をされていました。Go の文法と使い方は、関連記事「Go言語入門|1.27対応の基本文法を実行結果つきで解説」で扱っています。
コンパイルとビルドの違い(分割コンパイルとリンク)
この節では、コンパイルとビルドの違いと、ファイルが複数あるときに何が起きるのかが分かります。
ビルドは、コンパイル・リンク・必要なファイルの準備など、ソースコードから実行できる成果物を作る工程全体を指します。 e-Words も、コンパイルから実行ファイルの作成までの一連の工程をビルドとしています。
| 言葉 | 範囲 | 例 |
|---|---|---|
| コンパイル | ソースコードを機械語・バイトコードなどに変換する | gcc -c main.c |
| リンク | 部品(.o)とライブラリをつないで実行ファイルにする | gcc main.o calc.o -o app |
| ビルド | コンパイル+リンク+そのほかの工程の全体 | make・mvn package・go build |
| デプロイ | できた成果物をサーバーに置いて動かす | 本番サーバーへの配置 |
関数を calc.c に分けたプログラムで、リンクを試しました。
/* calc.h */
int sum_squares(int n);
/* main.c */
#include <stdio.h>
#include "calc.h"
int main(void) {
printf("sum=%d\n", sum_squares(10));
return 0;
}
calc.c を渡し忘れると、コンパイルは通り、リンクの段階で失敗しました。
$ gcc main.c -o app
/usr/bin/ld: /tmp/cc4AuYAH.o: in function `main':
main.c:(.text+0xe): undefined reference to `sum_squares'
collect2: error: ld returned 1 exit status
undefined reference(定義が見つからない)はリンクのエラーで、ld(リンカ)が出しています。 main.o の中では sum_squares が U(このファイルには無い)になっていて、つなぐ相手がいないためです。2つともコンパイルしてからつなぐと動きます。
$ gcc -c main.c && gcc -c calc.c && gcc main.o calc.o -o app && ./app
sum=385
make で「変わったファイルだけ」コンパイルし直す
ファイルが増えると、毎回すべてをコンパイルし直すのは時間がかかります。make などのビルドツールは、変更のあったファイルだけをコンパイルし直します。
app: main.o calc.o
gcc main.o calc.o -o app
main.o: main.c calc.h
gcc -c main.c
calc.o: calc.c calc.h
gcc -c calc.c
$ make # 1回目:全部コンパイル
gcc -c main.c
gcc -c calc.c
gcc main.o calc.o -o app
$ make # 何も変えずにもう一度
make: 'app' is up to date.
$ touch calc.c && make # calc.c だけ変えた
gcc -c calc.c
gcc main.o calc.o -o app
calc.c を変えたときは、calc.c だけをコンパイルし直して、リンクをやり直しました。 Go も同じ考え方で、コンパイルした結果をキャッシュに残します。手元では、キャッシュが空の状態の go build は6.75秒(標準ライブラリの fmt などもコンパイル)、2回目は0.05秒でした。
Makefile の書き方は、関連記事「Makefileの書き方|自動変数一覧とタスクランナー用テンプレート」で、ビルドしたものをサーバーに置く工程は、関連記事「デプロイとは|ビルド・リリースとの違いとロールバックを実例で解説」で扱っています。
JITコンパイルとは(実行中にコンパイルする)
この節では、JIT コンパイルが何をしているのかと、どれくらい速さが変わるのかが、実測で分かります。
JIT(Just-In-Time)コンパイルは、プログラムの実行中に、よく使われる部分を機械語に変換する方式です。 これに対し、実行の前にまとめて変換する方式を AOT(Ahead-Of-Time、事前)コンパイルと呼びます。e-Words も、事前コンパイル(AOT)と実行時コンパイル(JIT)を分けて説明しています。
| 方式 | 変換のタイミング | 主な例 |
|---|---|---|
| AOT(事前コンパイル) | 実行の前に全部 | C・C++・Go・Rust |
| JIT(実行時コンパイル) | 実行中に、よく使う部分だけ | Java の JVM・JavaScript の V8・.NET |
| インタプリタ | 実行しながら1命令ずつ解釈 | Python(標準の CPython) |
Java の JIT が動いた記録
-XX:+PrintCompilation を付けると、JVM がどのメソッドを機械語にしたのかが表示されます。
57 7 % 3 B::main @ 12 (48 bytes)
62 8 3 B::main (48 bytes)
63 9 % 4 B::main @ 12 (48 bytes)
左から「起動からのミリ秒・番号・印・段階・メソッド名」です。% はループの途中で機械語に切りかえたこと、3 は速くコンパイルする C1 コンパイラ、4 はじっくり最適化する C2 コンパイラを表します。起動から57ミリ秒後に、ループのあるメソッドが機械語に変換されていました。
Oracle の java コマンドのドキュメントでは、既定の -Xmixed は「よく使われるメソッドだけを機械語にし、それ以外はインタプリタで実行する」、-Xint は「機械語へのコンパイルを止め、すべてインタプリタで実行する」と説明されています。
JavaScript(V8)でも、--trace-opt を付けると、よく使われる関数が TurboFan で最適化された記録が出ました。
[marking 0x07ff9d8a4309 <JSFunction (sfi = 0x29563869ae19)> for optimization to TURBOFAN, ConcurrencyMode::kConcurrent, reason: hot and stable]
[completed optimizing 0x07ff9d8a4309 <JSFunction (sfi = 0x29563869ae19)> (target TURBOFAN) OSR]
方式による速さの違い(実測)
同じ計算(1 から 3,000万までの i×i を足して 1,000,003 で割った余り)を各言語で書き、3回ずつ測った中央値です。結果はすべて 761038 で一致しました。時間には、プログラムを起動する時間(JVM の起動など)も含みます。
読み取れることです。
- 事前に機械語にした Go・C が最も速く、JIT を使う Java はそれに近い速さでした。 起動の時間を含めても0.18秒です。
- 同じ Java でも、JIT を止める
-Xintでは約3.8倍(0.18秒→0.69秒)かかりました。 Node.js も JIT を止める--jitless(実行時に実行可能なメモリを確保しない設定)で約3.4倍(0.52秒→1.76秒)になりました。 - 標準の Python(CPython 3.11)は JIT を使わず、Go・C の約25倍の時間でした。 Python 3.13 の新機能の説明によると、3.13 で実験的な JIT コンパイラが入りましたが、ビルドするときに
--enable-experimental-jitを指定した場合だけで、既定では無効です。
この差はループの多い計算での結果です。ファイルの読み書きや通信を待つ時間が長いプログラムでは、言語の速さの差は小さくなります。
コンパイルエラーと実行時エラーの違い
この節では、エラーが「コンパイルのとき」と「実行のとき」のどちらで出るのかと、各言語の実際のエラー文が分かります。
エラーが見つかるタイミング
コンパイラが調べられるのは文法と型まで。0で割るなどは実行しないと分からない
| 比べる点 | コンパイルエラー | 実行時エラー |
|---|---|---|
| 出るとき | コンパイルのとき | 実行の途中 |
| 主な原因 | 文法の誤り・型の不一致・未定義の名前 | 0で割る・範囲外・null・ファイルが無い |
| 実行ファイル | できない | できている |
| 途中の処理 | 1行も動かない | エラーの手前までは動く |
コンパイルエラーの原文
C(gcc):セミコロンを忘れた
ce.c: In function 'main':
ce.c:6:5: error: expected ',' or ';' before 'return'
6 | return 0;
| ^~~~~~
誤りは5行目の int total = 10 の行末ですが、gcc は「次の6行目の return の前に ; が要る」と報告しました。エラーの行番号の1つ前の行も見るのがコツです。
Java(javac):型が合わない・名前のつづり違い
CeJava.java:4: error: incompatible types: String cannot be converted to int
int total = "10";
^
CeJava.java:5: error: cannot find symbol
System.out.println(totl);
^
symbol: variable totl
location: class CeJava
2 errors
Go:使っていない変数と import
# command-line-arguments
./ce.go:5:2: "os" imported and not used
./ce.go:10:2: declared and not used: total
Go は、使っていない変数や import もコンパイルエラーにします。
TypeScript(tsc):型が合わない
ce.ts(2,5): error TS2322: Type 'string' is not assignable to type 'number'.
tsc は、型のエラーがあっても既定では JavaScript を出力します。 実際に ce.js が作られ、node ce.js で動きました。TypeScript の公式ドキュメントによると noEmitOnError の既定は false で、--noEmitOnError を付けると、エラーがあるときは .js を作りませんでした。
Python:文法の誤り(コロンを忘れた)
print("start")
total = 10
if total > 5
print("big")
File "…/ce.py", line 3
if total > 5
^
SyntaxError: expected ':'
1行目の print("start") も表示されませんでした。 Python も実行の前にファイル全体をバイトコードにコンパイルするので、文法の誤りが1か所でもあると、1行も動きません。JavaScript(Node.js)でも同じで、エラーの記録に Module._compile と出て、start は表示されませんでした。
…/ce.js2.js:3
if (total > 5 {
^
SyntaxError: Unexpected token '{'
at wrapSafe (node:internal/modules/cjs/loader:1637:18)
at Module._compile (node:internal/modules/cjs/loader:1679:20)
実行時エラーの原文
引数の数で割る、という同じ誤りを入れたプログラムを、引数なし(0で割る)で実行しました。どれもコンパイルは通っています。
| 言語 | 表示された原文 | 終了コード |
|---|---|---|
| C | Floating point exception(bash の表示) |
136 |
| Java | Exception in thread "main" java.lang.ArithmeticException: / by zero |
1 |
| Go | panic: runtime error: integer divide by zero |
2 |
| Python | ZeroDivisionError: integer division or modulo by zero |
1 |
| TypeScript→JS | TypeError: Cannot read properties of undefined (reading 'toUpperCase') |
1 |
start
Traceback (most recent call last):
File "…/zerodiv.py", line 5, in <module>
print("1人あたり", 1000 // people)
~~~~~^^~~~~~~~
ZeroDivisionError: integer division or modulo by zero
実行時エラーでは、エラーの手前の start は表示されました。 コンパイルエラーとの大きな違いです。
TypeScript の例は、JSON.parse("{}") as User で「name がある」と型を決めつけたものです。tsc の型チェックは通りましたが、実行すると中身に name が無く、エラーになりました。型の情報はコンパイルで消えるので、外から来たデータの中身までは保証しません。
通らなかった行の誤りは見つからない(Python)
def report():
print(totl) # つづりの間違い(total のつもり)
total = 10
print("start")
if total > 100:
report() # この行は通らない
print("end")
start
end
totl のつづり違いがあっても、その関数が呼ばれなければエラーになりませんでした。 python3 -m py_compile も成功(終了コード0)しました。py_compile が見つけるのは文法の誤りだけで、名前のつづり違いは実行するまで分かりません。Java の javac は、同じ誤りを cannot find symbol としてコンパイルのときに見つけました。
- 1最初のエラーから直す1つの誤りが後ろのエラーを連鎖して出すことがある
- 2ファイル名・行番号・列番号を見るce.c:6:5 は6行目の5文字目。セミコロン忘れは1つ前の行
- 3エラーを出したのが誰かを見るld・collect2 ならリンク、Traceback なら実行時
- 4エラー文の英語をそのまま検索するexpected ';'・cannot find symbol など決まった文言が多い
- 5型チェックや静的解析で実行前に見つけるPython は mypy、JavaScript は TypeScript で、実行時の誤りの一部を前倒しできる
最適化とクロスコンパイル
この節では、コンパイラが速いコードを作る「最適化」と、ほかの OS・CPU 向けにコンパイルする方法が分かります。
最適化:答えが決まっている計算を先に済ませる
gcc に -O2 を付けてアセンブリ言語を出すと、main の中身は次のようになりました。
main:
endbr64
subq $8, %rsp
movl $385, %edx
movl $2, %edi
xorl %eax, %eax
leaq .LC0(%rip), %rsi
call __printf_chk@PLT
sum_squares(10) を呼ぶ命令もループも消え、答えの 385 がそのまま書かれていました。 引数が10で決まっているので、コンパイラがコンパイルの時点で計算を済ませたためです。-O0(最適化なし)では、前の節のとおりループの命令が残っていました。
| gcc のオプション | 意味 |
|---|---|
-O0 |
最適化しない(既定)。デバッグしやすい |
-O2 |
よく使われる最適化をする |
-O3 |
さらに強く最適化する |
-Os |
大きさが小さくなるように最適化する |
-g |
デバッガー用の情報を入れる |
クロスコンパイル:ほかの OS・CPU 向けに作る
コンパイルする PC とは別の OS・CPU で動くファイルを作ることを、クロスコンパイルと呼びます。Go は環境変数 GOOS(OS)と GOARCH(CPU)を指定するだけでできました。
GOOS=windows GOARCH=amd64 go build -o sumsq.exe sumsq.go
GOOS=darwin GOARCH=arm64 go build -o sumsq-mac sumsq.go
GOOS=linux GOARCH=arm64 go build -o sumsq-arm64 sumsq.go
GOOS=js GOARCH=wasm go build -o sumsq.wasm sumsq.go
file sumsq.exe sumsq-mac sumsq-arm64 sumsq.wasm
sumsq.exe: PE32+ executable (console) x86-64, for MS Windows, 15 sections
sumsq-mac: Mach-O 64-bit arm64 executable, flags:<|DYLDLINK|PIE>
sumsq-arm64: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, ...
sumsq.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
1台の Linux の PC から、Windows・Apple シリコンの Mac・ARM の Linux・ブラウザ(WebAssembly)向けのファイルができました。 機械語は CPU の種類ごとに違うので、同じソースコードでも CPU・OS ごとにコンパイルし直します。Java の .class や Python の .pyc は仮想マシン向けの命令なので、この作業が要りません。
よくある質問
Q. コンパイルとは簡単に言うと何ですか? A. 人が書いたプログラムを、コンピューターが実行できる形にまとめて変換することです。本を1冊まるごと翻訳してから渡す翻訳家のような役目で、変換するソフトをコンパイラと呼びます。
Q. コンパイルの読み方と英語の意味は? A. 読み方は「コンパイル」で、英語は compile です。英語ではもともと「資料を集めて編集する・編纂する」という意味の言葉です。
Q. コンパイラとインタプリタの違いは? A. コンパイラは実行の前に全体をまとめて変換し、インタプリタは実行しながら少しずつ解釈します。この記事の計算では、事前に機械語にした C・Go が約0.14秒、Python が約3.5秒でした。
Q. Python はコンパイルしなくてよいのですか?
A. コンパイルの命令を自分で打つ手順はありません。ただし Python は実行の前に自動でソースコードをバイトコードにコンパイルしています。import したファイルは __pycache__ に .pyc として保存されます。文法の誤りが1か所でもあると、1行も実行されないのはそのためです。
Q. Java はコンパイル言語ですか、インタプリタ言語ですか?
A. 両方を使います。javac で JVM 向けのバイトコード(.class)にコンパイルし、実行時は JVM がインタプリタで動かしながら、よく使う部分を JIT で機械語にします。JIT を止めると、この記事の計算は約3.8倍の時間がかかりました。
Q. JavaScript にコンパイルは必要ですか? A. 自分でコンパイルの命令を打つ手順はありません。Node.js やブラウザのエンジン(V8 など)が、実行の前にバイトコードに変換し、よく使う部分を JIT で機械語にします。TypeScript は tsc で JavaScript に変換してから実行します。
Q. コンパイルとビルドの違いは?
A. コンパイルはソースコードを変換する工程、ビルドはコンパイル・リンク・そのほかの準備を含めて実行できる成果物を作る工程全体です。C では、コンパイルが通ってもリンクで undefined reference のエラーになることがあります。
Q. トランスパイルとコンパイルの違いは? A. トランスパイルは、TypeScript から JavaScript のように、同じくらいの水準の別の言語へ変換することを指す言葉です。広い意味ではコンパイルの一種で、TypeScript の公式ドキュメントでも tsc を「TypeScript compiler」と呼んでいます。
Q. コンパイルエラーとは何ですか? A. コンパイルの段階で見つかる誤りです。文法の誤り・型の不一致・存在しない名前などが原因で、直すまで実行ファイルができません。0で割るなどの誤りは実行するまで分からず、実行時エラーになります。
Q. 機械語とアセンブリ言語の違いは?
A. 機械語は CPU が直接読む 0 と 1 の命令(16進数で 0f af c0 など)、アセンブリ言語はそれを人が読めるように imul %eax,%eax のような文字で書いたものです。gcc の -S でアセンブリ言語、objdump -d で機械語とアセンブリ言語の対応を見られます。
まとめ
- コンパイルは、人が書いたソースコードを、機械語やバイトコードなど実行できる形にまとめて変換すること。変換するソフトがコンパイラ。
- gcc は「プリプロセス → コンパイル本体 → アセンブル → リンク」の4段階。14行のソースが827行に展開され、16,000バイトの実行ファイルになった。
- 同じプログラムでも、C・Go は機械語の実行ファイル、Java は
.class、Python は.pyc、TypeScript は.js、JavaScript はファイルを残さずエンジンの中でバイトコードになる。 - ビルドはコンパイル・リンクを含む工程全体。部品が足りないとリンクで
undefined referenceになる。 - JIT は実行中によく使う部分を機械語にする方式。JIT を止めると Java は約3.8倍、Node.js は約3.4倍の時間がかかった。
- コンパイルエラーは実行前に見つかり1行も動かない。実行時エラーはその行に来たときに止まる。
参考・出典
確認日はいずれも 2026年10月23日です。
- MDN Web Docs「Compile (コンパイル)」:https://developer.mozilla.org/ja/docs/Glossary/Compile
- IT用語辞典 e-Words「コンパイル」:https://e-words.jp/w/コンパイル.html
- Weblio 英和辞書「compile」:https://ejje.weblio.jp/content/compile
- Wikipedia「コンパイラ」:https://ja.wikipedia.org/wiki/コンパイラ
- GCC「Options Controlling the Kind of Output」:https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html
- Oracle「The javac Command(JDK 21)」:https://docs.oracle.com/en/java/javase/21/docs/specs/man/javac.html
- Oracle「The java Command(JDK 21)」:https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html
- OpenJDK「JEP 330: Launch Single-File Source-Code Programs」:https://openjdk.org/jeps/330
- Python「Glossary(bytecode・interpreted)」:https://docs.python.org/3/glossary.html
- Python「What's New In Python 3.13」:https://docs.python.org/3/whatsnew/3.13.html
- V8「Ignition」:https://v8.dev/docs/ignition
- TypeScript「The Basics(tsc, the TypeScript compiler)」:https://www.typescriptlang.org/docs/handbook/2/basic-types.html
- TypeScript「TSConfig Reference(noEmitOnError)」:https://www.typescriptlang.org/tsconfig/#noEmitOnError
- Microsoft TypeScript Blog「Announcing TypeScript 7.0」:https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/
- Go「cmd/go(go build・go run)」:https://pkg.go.dev/cmd/go
- The Rust Programming Language「Hello, World!」:https://doc.rust-lang.org/book/ch01-02-hello-world.html
- Microsoft Learn「マネージド実行プロセス」:https://learn.microsoft.com/ja-jp/dotnet/standard/managed-execution-process
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「コンパイルとは|意味・インタプリタとの違いを6言語の実行で解説」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。