makeでコンパイルしよう
複数のソースファイルで構成された大きなプログラムをコンパイルする場合、一つのソースファイルを変更しただけですべてのソースファイルをコンパイルし直しててリンクするのは無駄です。
しかし、いちいちどのファイルが変更されたかチェックしてコンパイルするファイルを選択するのは面倒です。
こんな場合に便利なのがmakeコマンドです。
makeコマンドはソースファイルの更新日付をチェックして、自動的にコンパイルの必要なファイルを判断してくれます。
makeコマンドを実行するには、どのようにコンパイルするかをmakeコマンドに指示するための情報を記述した設定ファイルが必要です。
この設定ファイルの事をmakefileと言います。
makefileの記述方法
makefileの記述方法は以下のようになります。
ターゲットファイル : 依存ファイル (タブ)コマンド (空行) (上記設定の繰り返し。)
ターゲットファイルは通常、依存ファイルを基にコマンドリストに記述されているコマンドを実行した結果、生成されるファイルを指します。
例えば、「Cプログラム - はじめのいっぽ」の演習問題3で示した2つソースファイル「add.c」と「add_sub.c」から実行ファイル「add」を生成するには、以下のコマンドを実行しますが、
gcc add.c add_sub.c -o add
この場合、ターゲットファイルは「add」,依存ファイルはソースファイルである「add.c」と「add_sub.c」,コマンドは「gcc add.c add_sub.c -o add」になります。
この関係をmakefileに記述すると以下のようになります。
add : add.c add_sub.c gcc add.c add_sub.c -o add
タ-ゲットファイルと依存ファイルは:で区切って同じ行に記述します。
依存ファイルはスペースで区切って複数記述できます。
コマンドはその次の行の先頭にタブ文字を付けて記述します。
コマンドも;で区切って複数記述できます。
複数のコマンドの記述はダブを先頭に付けて次の行に続けて記述する事もできます。
ターゲットファリルと依存ファイルによる処理を、空行で区切って複数記述する事ができます。
makefileの指定
makefileを指定してmakeコマンドを実行するには以下のようにします。
make -f makefileのファイル名
-fを指定せずにmakeコマンドを実行するとカレントデレクトリの「makefile」というファアイルが指定された事になります。
単純な2つのcソースファイルからなるプログラムのコンパイル
先ほどの「makefileの記述方法」で示したmakefileの例では常に2つのソースファイルをコンパイルするのでmakeコマンドのメリットは全然ありません。
以下のようにmakefileを記述すると新たに変更が発生したソースファイルについてのみコンパイルを実行します。
add : add.o add_sub.o gcc add.o add_sub.o -o add add.o : add.c gcc -c add.c add_sub.o : add_sub.c gcc -c add_sub.c
最初の依存ファイルの設定では実行ファイルよりも2つのオブジェクトファイルのどちらかもしくは両方の日付が新しい場合のみリンクをおこなう事を指示します。
2番目と3番目の依存ファイルの設定では2つのソースファイルがそれぞれのオブジェクトファイルより新しい場合のみコンパイルをおこなう事を指示します。
以下のmakeコマンドの実行例では、ソースフィル「add_sub.c」がオブジェクトファイル「add_sub.o」より新しいため「add_sub.c」のみをコンパイルした後にリンクを実行しています。
touchコマンドによるファイル日付の変更
touchコマンドを使うとファイル日付を新しい日付に変更できます。
touchコマンドを使うとソースファイルを修正していなくてもファイル日付を新しくする事ができるので、makeコマンドで再コンパイルを実行するのに便利です。
touchコマンドの書式は以下のようになります。
touch ファイル名
日付を指定してtouchコマンドを実行する事もできます。
touch -t 日付 ファイル名
サフィックスルール
makemakeにはサフイックスルールという拡張子の関係を定義する事でmakefileを簡略化する仕組みが用意されています。
前記のmakefileの例のadd.oとadd.cの関係とadd_sub.oとadd_sub.cの関係は良く似ています。
拡張子(サフィックス)の関係はまったく同じです。
サフィックスルールをmakefileに定義するには、依存関係にあるサフィックスをターゲットファイルに指定して、依存ファイルは記述せずに依存関係が成立した時に実行するコマンドを記述します。
以下に示すmakefileの例の後のターゲットはソースファイル(サフィックス.c)とオブジェクトファイル(サフィックス.o)の関係をサフィックスルールを用いて記述したものです。
add : add.o add_sub.o gcc add.o add_sub.o -o add c.o : gcc -c $<
ここでは「$<」という特殊変数が使われてますが、特殊変数については後の項で述べます。
サフィックスルールを1つ記述するだけで、すべての同じサフィックスの依存関係に対して作用します。
暗黙の依存関係
実はmakeコマンドには暗黙の依存関係が存在し、サフィックス.oのとサフィックス.cの依存関係は暗黙の内に定義されています。
このため、前項で指定したオブジェクトファイルとソースファイルの依存関係の指定は省略する事ができます。
以下の例はオブジェクトファイルとソースファイルの依存関係を省略したmakefileの例です。
add : add.o add_sub.o gcc add.o add_sub.o -o add
ヘッダーファイルの依存関係の指定
通常Cのソースを2つに分解した場合には、リンクするソースのヘッダファイルを作成します。
以下のヘッダファイル「add_sub.h」を作成し「add.c」でヘッダファイル「add_sub.h」をincludeするようにプログラムを変更します。
add_sub.h
int add(int a, int b);
ヘッダファイルの追加によりmakefileは以下のように修正が必要になります。
add : add.o add_sub.o gcc add.o add_sub.o -o add add.o : add.c add_sub.h gcc -c add.c
ターゲットを指定してmakeコマンドを実行
依存ファイルを指定しないターゲットファイルも記述できます。
依存ファイルを記述しない場合、ターゲットファイルの処理時にコマンドは常に実行されるようになります。
ターゲットファイルには実際には存在しないターゲートファイルを指定する事もできます。
この場合もターゲットファイルの処理時にコマンドは常に実行されるようになります。
makefileは複数のターゲットファイルを記述できますが、デフォルトでは最初のターゲットから処理を始めます。
このため最初のターゲットファイルとまったく無関係のターゲットファイルの記述行は無視され何も処理されません。
特定のターゲットファイルから処理を開始したい場合にはmakeコマンドの引数にターゲットファイルを指定します。
make ターゲットファイル
これを利用すると複数の用途の処理を1つのmakefileに記述する事ができます。
以下のmakefileの例ではdisp_sourceという実際には存在しないターゲットファイルを指定してソースファイルをコンソールに表示する処理を追加しています。
makefile
add : add.o add_sub.o gcc add.o add_sub.o -o add add.o : add.c add_sub.h gcc -c add.c disp_source : cat add.c add_sub.c add_sub.h
実行例
実行例では最初のmakeコマンドの実行でターゲットファイル「disp_source」を指定しています。
このため、makefileの最初に記述されているターゲットファイル「add」の処理は「dip_souce」とは無関係のため無視されてソースファイルをコンソールに表示する処理のみが実行されます。
2番目のmake2番目のmakeコマンドではターゲットファイルが明示的に指定されていないため、最初のターゲットファイル「add」の処理が実行されます。
このためaddのコンパイル処理のみが実行されます。
makefileの変数
makefileには変数を使う事ができます。
変数を使う事でmakefileの中に同じ文字列を何度も記述しなくて済むようになります。
また、変数の値を変更する事で汎用性のあるmakefileを記述する事ができます。
変数を宣言するときには変数名をそのまま記述します。
それに対して変数を使う場合には、変数名の前に$を付けた後、変数名を括弧で囲みます。
変数を使用したmakefileの例を以下に示します。
OUTPUT_FILE = add OBJECT_FILES = add.o add_sub.o HEADER_FILES = add_sub.h add.c $(OUTPUT_FILE) : $(OBJECT_FILES) gcc $(OBJECT_FILES) -o $(OUTPUT_FILE) $(OUTPUT_FILE).o : $(HEADER_FILES) $(OUTPUT_FILE).c $(CC) -c $(OUTPUT_FILE).c
あらかじめ定義されている定義済みの変数も存在します。
定義済みの変数CCにはCコンパイラ(msysではgcc)が定義されています。
また、CFLAGS変数にはCコンパイラの標準で使用するオプションが定義されています。
定義済み変数は書き換え可能です。
msysではCFLAGS変数にはデフォルトで空文字になっていてCコンパイラのオプションが何も指定されていません。
CFLAGS変数に「-Wall」オプションを指定しておくと、危ういコードに対して警告を出力してくれるので正しいソースを書くための助けになり便利です。
その他にもいろいろと定義済み変数が存在します。
下の例は定義済み変数を使用してmakefileを書き換えた例です。
OUTPUT_FILE = add OBJECT_FILES = add.o add_sub.o HEADER_FILES = add_sub.h add.c CFLAGS=-Wall $(OUTPUT_FILE) : $(OBJECT_FILES) $(CC) $(CFLAGS) $(OBJECT_FILES) -o $(OUTPUT_FILE) $(OUTPUT_FILE).o : $(HEADER_FILES) $(OUTPUT_FILE).c $(CC) -c $(CFLAGS) $(OUTPUT_FILE).c
makefileの特殊変数
ターゲットと依存ファイルの関係を示す値が格納された、特殊変数が定義されています。
例えばサフィックスルールの項で述べた「$<」特殊変数は、サフィックスルールにおいてターゲットファイルより新しい依存ファイルを示します。
他にも代表的なものとして以下のような特殊変数が存在します。
- 「$@」は依存行のターゲットファイル名を示します。
- 「$^」はすべて「$^」は依存行のすべての依存ファイル名を示します。
- 「$?」はターゲットよりも新しい依存ファイル名を示します。
- 「$*」はサフィックスルールにおいてサフィックスの無いターゲットファイル名を示します。
この特殊変数を使うと前項のmakefileは下記のように記述できます。
OUTPUT_FILE = add OBJECT_FILES = add.o add_sub.o HEADER_FILES = add_sub.h add.c CFLAGS=-Wall $(OUTPUT_FILE) : $(OBJECT_FILES) $(CC) $(CFLAGS) -o $@ $^ $(OUTPUT_FILE).o : $(HEADER_FILES) $(OUTPUT_FILE).c $(CC) -c $(CFLAGS) $*.c
ターゲットcleanとall
makeコマンドで生成したファイルを削除するcleanというターゲットを定義しておくと、最初からすべてのファイルのコンパイルおよびリンクをやり直したい時に便利です。
また独立して複数のターゲットのmakeを連続しておこなう場合はallというターゲットを用意して、依存ファイルに複数のターゲットを指定します。
以下のmakefileではallの依存ファイルとしてcleanと$(OUTPUT_FILE)を指定していて、ターゲットとしてallを指定すると最初からすべてのファイルのコンパイルとリンクをやり直します。
OUTPUT_FILE=add OBJECT_FILES=add.o add_sub.o HEADER_FILES=add_sub.h add.c CFLAGS=-Wall $(OUTPUT_FILE) : $(OBJECT_FILES) $(CC) $(CFLAGS) -o $@ $^ $(OUTPUT_FILE).o : $(HEADER_FILES) $(OUTPUT_FILE).c $(CC) -c $(CFLAGS) $*.c clean : rm -f $(OBJECT_FILES) all : clean $(OUTPUT_FILE)
ターゲット名は必ずしもcleanとallという名前で無くてもかまいませんが、慣例としてこの名前がよく使われます。
多段make
複数のサブプロジェクトからなるプロジェクトでプログラムを開発する場合に、サブプロジェクトごとにデレクトリを作成して、その中に各サブプロジェクトのソースファイルとmakefileを保存しておいてファイルを管理すると管理がし易くなります。
そのような場合に、多段makeを使用すると複数のサブプロジェクトのmakefileを一度に実行するmakefileを作成する事ができるようになります。
以下にその例を示します。
複数のプロジェクトのmakeを実行するmakefileをサブプロジェクトのデレクトリの親デレクトリに作ります。
parent_project : make -C sub_project1 make -C sub_project2
sub_project1,sub_project2はサブプロジェクトのソースファイルとmakefileを保存しているデレクトリとします。
上記のmakefileを実行すると以下のようにサブプロジェクトのデレクトリに移動して各サブプロジェクトのmakefileを実行します。
デレクトリ移動の際のメッセージ、Entering directory,Leaving directoryを表示させないようにするには「make -C」を「make --no-print-directory -C」と変更します。
あとがき
以上、makeコマンドによるc/c++のコンパイルについて簡単にまとめてみました。
makeコマンドは非常に多機能でライブラリのmake方法等、ここでは紹介していないまだまだ多くのトピックがあります。