コンパイルのしくみ

cc (gcc)は,ソースファイルをコンパイルして実行ファイル a.out を生成するコマンドです.cc が行なうコンパイル処理には,実際には次に挙げる複数の処理が含まれています.

  1. 前処理 (cpp)
  2. コンパイル (ccom)
  3. アセンブル (as)
  4. リンク (ls)

括弧内に記したものは,それぞれの処理を受け持つコマンドです.cc は,実際にはこれらのコマンドを次々と呼び出すことによってコンパイル処理を行ないます.この様子を Fig. 1-1 に示します.

図中,コマンドとコマンドの間に記されたものは,直前のコマンドが生成する中間ファイルです.cc による処理が終了した時点でこれらの中間ファイルは消去されます(あるいはコマンド間で直接データが受け渡され,中間ファイルは作られない場合もあります)が,cc にオプションを設定すれば,各々の中間ファイルを得ることができます.次に,個々のコマンドについて説明します.

 1. 前処理

コマンド cpp はプリプロセッサと呼ばれます.例えばソースファイル sample.c の場合,その第1行:

#include <stdio.h>

はファイル stdio.h をこの場所に取り込むことを cpp に指示します(このようにプログラムの初めの部分で取り込むファイルのことを,ヘッダファイルまたはインクルードファイルと呼びます).ヘッダファイル stdio.h はディレクトリ /usr/include 以下にあります.cpp は,#include <...> という行(include文と呼びます)を見つけると,/usr/include 以下に < > で囲まれたファイル名のファイルを探し,もしあれば,そのファイルを include文と置き換えます(もしなければエラーになります).

ヘッダファイル stdio.h には sample.c 内で引用している関数 printf() の定義などが含まれています.stdio.h はほとんどのプログラムにおいて取り込む必要があります.

プリプロセッサは,ファイルの取り込みの他にマクロの展開コメントの除去などを行ないます.

マクロの展開とは,マクロ名定義された文字列に置換する操作です.プログラマーは,例えば

#define MAX 100

という行(define文と呼びます)をプログラムの最初の部分に書くことができます.このとき cpp はプログラム中でマクロ名 "MAX" を見つける度に,そのマクロ名を定義された文字列 "100" に置き換えてゆきます.

コメントとは,/* と*/ で囲まれた文字列です.コメントにはC言語の文法に関わりなく自由に注釈を書くことができます.cpp はすべてのコメントを除去します.ただし,コメントの入れ子(/*...*/ の中に /*...*/ があること)は禁止されています.

 2. コンパイル

コマンド ccom はコンパイラの本体です.ccom はプリプロセッサの出力を解析し,アセンブラプログラムを生成します.ここにアセンブラプログラムとは,アセンブリ言語(機械語と1対1に対応した,最も低水準な言語)によって表現されたプログラムのことです.アセンブラプログラムはソースプログラムの一種です.したがって,プログラマーが作成/編集することができます.

 3. アセンブル

コマンド as はアセンブラと呼ばれます.アセンブラは アセンブラプログラムを読み込んでオブジェクトプログラムを生成します.ここにオブジェクトプログラムとは,機械語(文字によって表現できない1と0からなるコード)によって表現されたプログラムのことです.コンパイラの目的(オブジェクト)がソースプログラムから機械語によるプログラムを生成することなので,オブジェクトプログラムと呼ばれます.なお,オブジェクトプログラムを納めたファイルをオブジェクトファイルと呼びます.

 4. リンク

コマンド ld はリンカと呼ばれます.リンカの役目はオブジェクトプログラムどうしを結合(リンク)し,実行ファイルを生成することです.一般にC言語のプログラムでは,関数によってひとまとまりの処理を表わし,この関数を必要に応じて引用することによってプログラムを構成します.あるオブジェクトプログラムが引用している関数の本体が別のオブジェクトプログラムに存在する場合,両オブジェクトプログラムを結合する必要が生じます.

関数は次のように表現します.

関数の型 関数名 ( 引数 ) { 処理手順 }

関数の型 とは,関数が出力する値の種類です.例えば,整数値 x を与えれば x + 5 を計算する関数の定義(関数名 plus5)は次のようになります.

int plus5 (int x) { return (x + 5); }

(int は整数:integerを表わします.)この関数 plus を引用する場合,例えば 6 + 5 を求めたい場合は,次のようにします.

z = plus5 (6);

これにより,関数 plus が 6 + 5 = 11 を計算し,得られた値を変数 z に代入します(z はあらかじめ整数型の変数として宣言しておく必要があります).

実行ファイルが起動されると,スタートアップルーチンと呼ばれる特別なプログラムがまず起動し,その内部で関数 main() が引用されます.したがって,実行ファイルを生成するには関数 main() を含むプログラムが必要です.

どのプログラムにも 関数 main() の本体が含まれています.一方,printf() など標準関数と呼ばれる関数は,その本体は標準ライブラリlibc.a に含まれています.標準ライブラリはオブジェクトファイルです.リンカ ld は,sample.c から生成されたオブジェクトプログラムとオブジェクトファイル libc.a を結合して実行ファイル a.out を生成します.