私たちが書いたC言語のソースコード(.c
ファイル)は、ただのテキストファイルです。それなのに、なぜ「ビルド」や「コンパイル」をすると、クリックすれば動く実行ファイル(Windowsの.exe
ファイルなど)に変身するのでしょうか?
この記事では、その魔法のような過程である**「ビルドプロセス」**を4つのステップに分解し、初心者の方にも分かりやすく、その舞台裏を徹底解説します。
実行ファイルができるまでの全体像
C言語のソースコードが実行ファイルになるまでの工程は、大きく分けて以下の4つのステップで構成されています。
- プリプロセス (Preprocessing):コンパイルの”準備体操”
- コンパイル (Compilation):人間の言葉を”アセンブリ言語”へ翻訳
- アセンブル (Assembly):アセンブリ言語を”機械語”へ変換
- リンク (Linking):バラバラの部品を合体させて”完成品”へ
この一連の流れを「ビルド」と呼びます。それでは、各ステップを一つずつ見ていきましょう。
ステップ1:プリプロセス – コンパイルの準備体操
プリプロセスは、本格的な翻訳作業であるコンパイルを始める前の下準備を行う工程です。担当するのはプリプロセッサです。
ソースコード内にある#
で始まる命令(プリプロセッサディレクティブ)を処理します。
#include
の処理:#include <stdio.h>
のような記述を見つけると、stdio.h
というヘッダファイルの中身をソースコードのその場所にそのまま展開(コピー&ペースト)します。#define
の処理:#define PI 3.14
のように定義されたマクロを見つけると、コード中のPI
という文字列をすべて3.14
に置き換えます。- コメントの削除:
//
や/* */
で書かれたコメントは、プログラムの動作には不要なため、この段階で全て削除されます。
この準備体操が終わると、C言語として純粋なコードだけが残った、一時的なファイルが生成されます。
ステップ2 & 3:コンパイルとアセンブル – 機械語への翻訳
次に、準備が整ったソースコードをコンピュータが理解できる言葉(機械語)へと翻訳していきます。この工程は、コンパイラとアセンブラが担当します。
ステップ2:コンパイル
コンパイラは、C言語のコードを、機械語に非常に近い低水準の言語である**「アセンブリ言語」**に翻訳します。アセンブリ言語は、「メモリのこの場所にこの数値を足せ」といった、CPUに対する命令が人間にも少し読みやすく記述されたものです。
ステップ3:アセンブル
アセンブラは、そのアセンブリ言語を、完全にコンピュータが理解できる0と1の羅列である**「機械語(マシン語)」**に変換します。
この機械語に、他のプログラム部品と合体するための情報などを付加したものが**「オブジェクトファイル」**です。Windows環境では.obj
、Linuxなどでは.o
という拡張子が一般的です。
オブジェクトファイル(.obj)とは一体何?
オブジェクトファイルは、一言でいえば**「未完成のプログラム部品」**です。
ソースコードの一部が機械語に翻訳された状態ですが、この時点ではまだ単独で動くことはできません。例えば、printf
関数を使っているコードをコンパイルした場合、オブジェクトファイルには「printf
という名前の関数を呼び出す」という指示は書かれていますが、printf
関数の本体(画面に文字を表示する処理の機械語)は含まれていません。
これらの部品を集めて、最終的なプログラムを組み立てるのが次の「リンク」の役割です。
ちなみに.tds
ファイルとは?
ご提示の資料にあった.tds
ファイルは、Turbo Debugger Symbol file の略で、主に1990年代に使われたBorland(ボーランド)社の開発環境(Turbo C++など)が生成するデバッグ用の情報ファイルです。 現代の主要な開発環境(Visual StudioやGCCなど)では、デバッグ情報は.pdb
という別のファイルに格納されたり、オブジェクトファイル自体に埋め込まれたりするのが一般的なので、現在ではほとんど見かけることはありません。
ステップ4:リンク – バラバラの部品を一つに合体!
最後のステップがリンクです。その名の通り、バラバラの部品を繋ぎ合わせる工程で、リンカが担当します。
リンカは、私たちが作ったプログラムのオブジェクトファイル(.obj
)と、printf
のような標準関数や様々な機能が詰まった**「ライブラリファイル」**とを結合します。
オブジェクトファイルに残されていた「printf
を呼び出す」という指示を、ライブラリファイル内にあるprintf
関数の本体(実体)と結びつけ、最終的に一つの実行可能なファイル(.exe
など)を生成します。
複数のソースファイル(例:player.c
, enemy.c
, main.c
)に分けてプログラミングした場合も、それぞれがオブジェクトファイル(player.obj
, enemy.obj
, main.obj
)になった後、このリンクの段階で一つに結合されます。
まとめ
C言語のソースコードが実行ファイルになるまでの旅を振り返ってみましょう。
- プリプロセッサが、
#include
の展開など、コンパイルの準備をします。 - コンパイラが、C言語をアセンブリ言語に翻訳します。
- アセンブラが、アセンブリ言語を機械語に変換し、未完成な部品であるオブジェクトファイル(
.obj
)を作ります。 - リンカが、オブジェクトファイルやライブラリを結合し、完成品である実行ファイル(
.exe
)を生成します。
この流れを理解しておくと、「リンクエラー」のようなエラーメッセージが出たときに、「ああ、部品同士の結合に失敗したんだな」と、問題の原因を推測する助けになります。一見複雑に見えるプログラムの裏側ですが、このようにステップごとに見ていくと、その仕組みが少し身近に感じられるのではないでしょうか。