C
AdventCalendar
Makefile
AdventCalendar2015
C言語Day 23

C言語を使うならMakeを使おうよ

次の理由からいろいろとムズムズしてしまったので、Makeについて書くことにしました。


  1. C言語 Advent Calendar 2015で2日分だけ空きがある。空きが埋まっていればめでたいし、空きがもっと多ければあきらめがつくが…^^;;;


  2. C言語でシンプルすぎるブロック崩しを書いた(C言語 Advent Calendar 2015 13日目)は、プログラムも記事も面白いのに、Makefileの書き方が好みではない


  3. make触ってみた(ドワンゴ Advent Calendar 2015 20日目) は参考になる情報が多い良記事だけど、結末が好みではない

なお、ここで取り上げるMakeは、GNU Makeです。Mac OS Xでコマンドラインツールをインストールしたときや、Linuxでは、makeコマンドを実行すると、GNU Makeが呼び出されるはずです。

また、以下の記述ではMakeの基本的な文法などについては説明しないので、必要に応じて「make触ってみた」記載のリンクや本記事で紹介している書籍などを参照してください。


hello.cのコンパイル — 1つのソースファイルをコンパイル

たとえば、次のhello.cというC言語ソースファイルを作成し、実行することを考えます。


hello.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 hellohelloを省略

make helloと入力するのは面倒くさい、makeだけでコンパイルしたいという場合は、Makeの設定ファイルであるMakefileをhello.cと同じディレクトリーに作成します。


Makefile

hello:


ここで設定されたhello:helloの部分をターゲットと呼び、makeで作成されるファイルをあらわします。Makefileに記述されたターゲットはmake実行時に、たとえばmake helloのような形で指定します。Makefileには複数のターゲットを指定することもできます。

Makefileで一番最初に記述されたターゲットは、make実行時に省略することができます。そのため、hello:だけをMakefileに記述した場合、makemake helloと同じ意味になります。

$ rm -f hello

$ make
cc hello.c -o hello


ccではなくgccでコンパイル

Makefileの記述により、コンパイル方法の詳細を設定できます。

たとえば、コンパイル時にccではなくgccを使う場合は、Makefileで変数CCを設定します。


Makefile

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ファイルを削除できます。


Makefile

hello:

clean:
$(RM) hello


make cleanを使うことは、rmコマンドで誤って必要なファイルを削除してしまう危険性を減らすメリットもあります。

なお、$(RM) helloの前はタブ文字です。タブ文字の代わりにスペース文字を入力するとmakeが正常に動作しませんので、注意して下さい。

また、cleanターゲットの記述は、hello:の記述よりも後にして下さい。


gccにオプションを付けてコンパイル

gccにオプション-g -Wall -Wextraを付ける場合は、変数CCCFLAGSを設定します。


Makefile

CC := gcc

CFLAGS := -g -Wall -Wextra

hello:

clean:
$(RM) hello


このとき、make cleanmakeを実行すると次のようになります。

$ make clean

rm -f hello
$ make
gcc -g -Wall -Wextra hello.c -o hello


シンプルすぎるブロック崩しのコンパイル — 複数に分割されたソースファイルをコンパイル

次に、シンプルすぎるブロック崩し のコンパイルを考えます。シンプルすぎるブロック崩しは、helloとは異なり、ソースファイルが複数に分割されています。また、ヘッダーファイルも存在します。こうした場合でも、Makeの組み込みルールを使えば、Makefileは簡潔に記述できます。


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.ofunction.oから実行可能ファイルblockを作成するようここで定義しています。このblock.ofunction.oは、block.cfunction.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

また、CFLAGSCPPFLAGSLDFLAGSTARGET_ARCHはいずれも空文字列です。

ここまでの結果をまとめると、初期状態では次のルールが設定されていることになります。

%: %.c

cc $^ -o $@

そのため、hello.cからhelloを作成するときには、次のコマンドが実行されます。

cc hello.c   -o hello





  1. OSやインストールされているコンパイラなど、環境に応じてコンパイルするコマンドは異なります。ccコマンドは多くの環境で利用可能なコンパイルコマンドです。たとえば、GCCがインストールされたLinuxではgccコマンドに、現行のMacOSXで開発環境を構築した場合にはclangコマンドに、それぞれリンクされていることが一般的です。