🐧 Linux 総合学習プラットフォーム
C・ビルドツール ・ 中級〜上級

Makefile実践——変数・パターンルール

前回のMakefile基本では、決め打ちのターゲットを並べるだけでした。しかし複数のソースファイルを扱う実際のプロジェクトでは、それでは行が増えるばかりで破綻します。ここでは変数・自動変数・パターンルール・.PHONYを使い、ファイルが増えても書き換えずに済むMakefileの作り方を学びます。あわせて、ヘッダを直したときにきちんと再ビルドされる依存関係の考え方も押さえます。少し複雑に見えますが、型さえ覚えれば一生使い回せる書き方です。

前回のMakefileは、ターゲットと材料をひとつずつ手書きした。ファイルが2つ3つのうちはそれでよいが、10個・20個になると同じような行を延々コピーする羽目になり、書き間違いも増える。

🔗
たとえ手書きのMakefileは、レシピの材料欄に「玉ねぎ1個」「玉ねぎ2個」「玉ねぎ3個」と個別に書くようなものだ。本当は「玉ねぎ×人数分」と1行で済ませたい。変数とパターンルールは、その「×人数分」を実現する仕組みだ。

🧮 変数でコンパイラとオプションをまとめる

Makefileの中でも CC = gcc のように書けば、CC という変数にgccという値を代入できる。以降 $(CC) と書けば、その場所にgccが展開される。CFLAGS = -Wall -g のようにコンパイルオプションも同じ形で変数化できる。

app: main.o $(CC) $(CFLAGS) -o app main.o のように書いておけば、コンパイラやオプションを変えたいときはMakefile先頭の2行を書き換えるだけで済む。変数を使っていなければ、全ターゲットのgccという文字列を1つずつ探して直すことになる。

💡
ポイント変数はMakefile全体の「共通設定」を1箇所に集める。散らばった同じ値を1つにまとめることで、変更に強くなる。

代入には = と := の2種類があり、= は使われる瞬間に右辺を評価する遅延代入、:= はその場で即座に評価する即時代入だ。予測しやすくしたいときは := を既定にしておくと事故が少ない。

🎯 自動変数——$@ $< $^ の三兄弟

ルールの右辺でターゲット名やファイル名を何度も書き写すのは面倒でコピペミスの元にもなる。Makeにはこれを自動で埋めてくれる自動変数がある。

$@ はそのルールのターゲット名、$< は最初の依存ファイル、$^ は依存ファイルすべてを空白区切りで並べたものに展開される。この3つ(cba-automatic-var)を覚えるだけで、ルールが一気に短く書ける。

app: main.o util.o gcc -o $@ $^$@ = appターゲット名$^ = main.o util.o依存の全部$< = main.o依存の先頭だけ同じルールでも $@ $< $^ で意味が変わる

迷ったら、ターゲット名は $@、材料全部は $^、最初の1個だけなら $< と覚えると使い分けやすい。

🔁 パターンルールで.cを一気に.oへ

自動変数と組み合わせて真価を発揮するのがパターンルール(cba-pattern-rule)だ。%.o: %.c と書けば「.cから同名の.oを作る」というルールを1つで表現できる。

%.o: %.c $(CC) $(CFLAGS) -c $< -o $@ この1行だけで、main.c → main.o、util.c → util.o のように対象の.cファイル全部の変換ルールを兼ねられる。ファイルが増えても行は増やさなくてよい。
%.o: %.c1つのパターンルールmain.c→main.outil.c→util.oio.c→io.o該当する.c全部に自動的に適用される
💡
ポイントパターンルールは「拡張子どうしの変換規則」を1回書けば、対象ファイルが何個あっても使い回せる。ファイルが増えてもMakefile本体はほぼ変わらない。

🏷️ .PHONYで「ファイルではない」と教える

clean や all は、実際のファイルを作るためではなく単なる操作名として使うことが多い。ところが同じ名前のファイルがたまたま存在すると、makeは「もう最新だから何もしない」と誤解してしまう。

この事故を防ぐのが .PHONY だ。.PHONY: clean all のように宣言しておくと、clean や all は実在ファイルと関係のない「常に実行される名前」として扱われる(cba-phony-target)。

.PHONY: all clean clean: rm -f *.o app こう書いておけば、うっかり clean というファイルを作ってしまっても make clean は正しく動く。

付け忘れても多くは問題なく動くが、同名ファイルが紛れ込んだ瞬間に「なぜか実行されない」という分かりにくいバグになる。clean・allなど操作系には習慣として付けておくとよい。

🔗 依存関係——ヘッダを直したら再ビルドされるように

ここまでは.cファイルだけを見てきたが、実際には.hヘッダファイルも変更する。困るのは、ヘッダだけを直してmakeを実行しても、対応する.oが古いままだと見なされず再コンパイルされないケースだ。

原因は単純で、Makefileのルールに書いた依存が main.o: main.c のように.cしか含んでいないからだ。makeは書かれた依存しか見ないため、ヘッダが変わったことに気づけない。

main.o: main.c common.h $(CC) $(CFLAGS) -c main.c -o main.o このように依存にcommon.hを加えておけば、ヘッダを触った瞬間にmain.oが古いと判定され、再コンパイルの対象になる。
🔗
たとえ依存関係は「材料リスト」だ。レシピに書かれていない食材が傷んでいても、レシピ側は気づけない。ヘッダをリストに載せておいて初めて、makeは「これも材料の一部」と認識できる。
コツ手で全部書くのは大きなプロジェクトでは大変なので、実務では gcc -MM でヘッダ依存を自動生成する方法もよく使われる。まず手書きで仕組みを理解しておくと、自動化されたMakefileも迷わず読める。

🧱 小さなプロジェクトの雛形

ここまでの要素を合わせると、複数の.cファイルと共通ヘッダを持つ小さなプロジェクトのMakefileは、次のような形にまとまる。

CC = gcc CFLAGS = -Wall -g OBJS = main.o util.o io.o app: $(OBJS) $(CC) $(CFLAGS) -o $@ $^ %.o: %.c common.h $(CC) $(CFLAGS) -c $< -o $@ .PHONY: clean clean: rm -f $(OBJS) app 変数・パターンルール・自動変数・.PHONYがそれぞれの役割で1回ずつ登場する、いわば全部入りの型だ。

この形を手元に置いておけば、ファイルが増えても OBJS の行に名前を足すだけでよい。次は、生み出したオブジェクトファイルを他のプログラムからも使えるライブラリという形にまとめる方法に進む。

この項目に出てくる用語

自動変数じどうへんすう
Makeのルール内でターゲット名や依存名に自動的に置き換わる特殊な変数。$@ $< $^ など。
パターンルールぱたーんるーる
%.o: %.c のように拡張子どうしの変換規則を1行でまとめて表すMakefileの書き方。
フォニーターゲットふぉにーたーげっと
.PHONYで宣言する、実ファイルと対応しない「操作名」としてのMakeターゲット。

関連コマンド

make

▶ 学習アプリでこの続きを学ぶ・演習する