次の理由からいろいろとムズムズしてしまったので、Makeについて書くことにしました。
- C言語 Advent Calendar 2015で2日分だけ空きがある。空きが埋まっていればめでたいし、空きがもっと多ければあきらめがつくが…^^;;;
- C言語でシンプルすぎるブロック崩しを書いた(C言語 Advent Calendar 2015 13日目)は、プログラムも記事も面白いのに、Makefileの書き方が好みではない
- make触ってみた(ドワンゴ Advent Calendar 2015 20日目) は参考になる情報が多い良記事だけど、結末が好みではない
なお、ここで取り上げるMakeは、GNU Makeです。Mac OS Xでコマンドラインツールをインストールしたときや、Linuxでは、makeコマンドを実行すると、GNU Makeが呼び出されるはずです。
また、以下の記述ではMakeの基本的な文法などについては説明しないので、必要に応じて「make触ってみた」記載のリンクや本記事で紹介している書籍などを参照してください。
hello.cのコンパイル — 1つのソースファイルをコンパイル
たとえば、次のhello.cというC言語ソースファイルを作成し、実行することを考えます。
#include <stdio.h>
int main() {
puts("hello, world!");
return 0;
}
普通は次のコマンドでコンパイルし、実行可能ファイルhelloを作成することになるでしょう1。
$ cc -o hello hello.c
$ ./hello
hello, world!
ところが、(GCCなどとともにGNU Makeがインストールされていることが前提ですが)hello.cファイルの作成以外ほかの準備をしなくても、次のコマンドでコンパイルができるのです。
$ rm -f hello
$ make hello
cc hello.c -o hello
$ ./hello
hello, world!
hello
の部分は、元の.cファイルの名前にあわせて記述します。
MakeはC言語のコンパイルで長年使われてきた歴史を反映し、さまざまなパターンでのコンパイルに対応しています。拡張子.cのファイルをコンパイルして拡張子を除いて同名のファイルを作成するルールが組み込まれ、make hello
だけでコンパイルができるのも、その表れです。
Makefileの作成
make hello
のhello
を省略
make hello
と入力するのは面倒くさい、make
だけでコンパイルしたいという場合は、Makeの設定ファイルであるMakefileをhello.cと同じディレクトリーに作成します。
hello:
ここで設定されたhello:
のhello
の部分をターゲットと呼び、make
で作成されるファイルをあらわします。Makefileに記述されたターゲットはmake
実行時に、たとえばmake hello
のような形で指定します。Makefileには複数のターゲットを指定することもできます。
Makefileで一番最初に記述されたターゲットは、make
実行時に省略することができます。そのため、hello:
だけをMakefileに記述した場合、make
はmake hello
と同じ意味になります。
$ rm -f hello
$ make
cc hello.c -o hello
cc
ではなくgcc
でコンパイル
Makefileの記述により、コンパイル方法の詳細を設定できます。
たとえば、コンパイル時にcc
ではなくgcc
を使う場合は、Makefileで変数CC
を設定します。
CC := gcc
hello:
このとき、make
を実行すると…
その前にちょっと注意。Makeは、必要最小限の作業だけを行うように設定されているため、hello.cファイルが更新されない限りhelloファイルは更新の必要がないと見なされ、再作成されません。なので、Makefileを更新してmake
を再実行する場合は、hello.cファイルを更新する(実際に更新しなくてもtouch hello.c
でファイル更新日時を変更すればOK)か、helloファイルを削除するかします。
helloファイルを削除してからmake
を実行すると、次のようになります。
$ rm -f hello
$ make
gcc hello.c -o hello
また、Makefileに次のようなcleanターゲットを追加すれば、make clean
でhelloファイルを削除できます。
hello:
clean:
$(RM) hello
make clean
を使うことは、rm
コマンドで誤って必要なファイルを削除してしまう危険性を減らすメリットもあります。
なお、$(RM) hello
の前はタブ文字です。タブ文字の代わりにスペース文字を入力するとmake
が正常に動作しませんので、注意して下さい。
また、cleanターゲットの記述は、hello:
の記述よりも後にして下さい。
gcc
にオプションを付けてコンパイル
gcc
にオプション-g -Wall -Wextra
を付ける場合は、変数CC
とCFLAGS
を設定します。
CC := gcc
CFLAGS := -g -Wall -Wextra
hello:
clean:
$(RM) hello
このとき、make clean
とmake
を実行すると次のようになります。
$ make clean
rm -f hello
$ make
gcc -g -Wall -Wextra hello.c -o hello
シンプルすぎるブロック崩しのコンパイル — 複数に分割されたソースファイルをコンパイル
次に、シンプルすぎるブロック崩し のコンパイルを考えます。シンプルすぎるブロック崩しは、helloとは異なり、ソースファイルが複数に分割されています。また、ヘッダーファイルも存在します。こうした場合でも、Makeの組み込みルールを使えば、Makefile
は簡潔に記述できます。
LDLIBS := -lncurses
block: block.o function.o
block.o function.o: function.h
clean:
$(RM) *.o
$(RM) block
Makeでは、拡張子.oのファイルから拡張子のない実行可能ファイルを作成する方法も定義されています。今回はコンパイル時にオプション-lncurses
を付ける必要があるため、変数LDLIBS
に設定しています。
Makeでは.oファイルから実行可能ファイルを作成する方法はあらかじめ定義されていても、どの.oファイルから実行可能ファイルを作成するのかはわからないため、block: block.o function.o
によりblock.o
とfunction.o
から実行可能ファイルblock
を作成するようここで定義しています。このblock.o
とfunction.o
は、block.c
とfunction.c
から必要に応じて作成されます。Makeでは、組み込みで.cファイルから.oファイルを作成するルールが設定されています。ルールが設定されている場合にファイルを必要に応じて自動作成できるのは、Makeの優れた特長です。
また、block.o function.o: function.h
は、ヘッダーファイルを更新したあと、その変更を実行可能ファイルに反映するために必要な記述です。
このMakefile
を作成後、次のようにコンパイルを実行できます。
$ make
cc -c -o block.o block.c
cc -c -o function.o function.c
cc block.o function.o -lncurses -o block
まとめ — Makeはそれほどむつかしくないはず
ここまでとりあげたMakefileはいずれも数行で終わるもので、C言語の複雑なプログラムを書くような方からすれば簡単なはずです。上記のmake触ってみたで取り上げているPHP7なんかは、
- プログラム自体が巨大かつ複雑
- 世界中のさまざまな環境への適応を考えなければならない
といった事情があり、Makefileも複雑化し、いろいろな工夫やトリックが必要となっているいるわけです。そこまで複雑でないC言語プログラムを書くときには、ずっと単純なMakefileで十分役に立つはずです。というわけで、Makeは一度は自分で書いてほしいし、多少無理だと思っても是非使ってほしいなと思っています。特にC言語の場合ですと、先人たちの切り開いた道があちこちに残されていますので。
お薦めの書籍
Makeについてのインターネット上の情報は、make触ってみたが参考になるかと思います。ただし、Makeについてはその枯れ具合ゆえインターネット上の情報よりも書籍の方が頼りになる場面が多いかもしれません。お薦めは次の2冊です。
- 『GNU Make』 GNU Makeの基礎から高度な使いこなしまで。さすがO'reilly、頼れる1冊です。
- 『UNIXプログラミング環境』 第8章でC言語とパーサー(YACC)によるプログラム開発を取り上げており、その中でMakeについても詳しく解説しています。古くて難解だけど、楽しくてためになります。
補足 — Makeのルールを確認する
.cファイルや.oファイルから実行可能ファイルを作成したり、.cファイルから.oファイルを作成したりするMakeのルールを確認するためには、make -p
コマンドを実行し、makeのデータベースを表示します。makeのデータベースにはかなり多くの内容が含まれるので、一時ファイルに保存してテキストエディターなどで開いたり、LESSなどのページャーを使うなどするとよいと思います。
たとえば、.cファイルから拡張子のない実行化のファイルを作成するルールを確認するには、make -p
コマンドの実行結果から%: %.c
と記載された部分を検索します。
%: %.c
# commands to execute (built-in):
$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
この定義の中で$@
はターゲットファイル、$^
は必須ファイル(ソース)をあらわします。たとえばhello.c
からhello
を作成する場合、必須ファイルはhello.c
、ターゲットファイルはhello
です。
次に、LINK.c
の定義は次のようになっています。
LINK.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
さらに、CC
の定義は次のようになっています。
CC = cc
また、CFLAGS
、CPPFLAGS
、LDFLAGS
、TARGET_ARCH
はいずれも空文字列です。
ここまでの結果をまとめると、初期状態では次のルールが設定されていることになります。
%: %.c
cc $^ -o $@
そのため、hello.c
からhello
を作成するときには、次のコマンドが実行されます。
cc hello.c -o hello
-
OSやインストールされているコンパイラなど、環境に応じてコンパイルするコマンドは異なります。
cc
コマンドは多くの環境で利用可能なコンパイルコマンドです。たとえば、GCCがインストールされたLinuxではgcc
コマンドに、現行のMacOSXで開発環境を構築した場合にはclang
コマンドに、それぞれリンクされていることが一般的です。 ↩