make (C言語編)

●注意● 基本編でも述べたが、 Makefileのコマンド行の先頭は TABで空ける。 このページのサンプルでは「→」で表現しているが、 本当に「→」を書いてはいけない。

はじめに

make はもちろん、プログラム開発でも利用される。 大規模なプログラム開発では、 一つのプログラムが数百のソースファイルに分割されていることも珍しくない。 このようなケースでは、コンパイルに数時間かかることもある。 一つのファイルをちょっと修正しただけで毎回 数時間もコンパイルするのでは あまりにも非効率である。 また、makeのマクロ定義などをうまく利用すれば、 コンパイルオプションの集中管理もできる。 ここでは、Cのプログラム開発に makeを使用した例を示す。 (なお、統合開発環境では、 同じことを「プロジェクトの管理」という名前で行なっている。)

ファイルが一つの場合

ソースファイルが一つだけの場合には、 Makefile は次のような感じになる。

runrun : runrun.c
→gcc -Wall -o runrun runrun.c -lm

コンパイラ名、コンパイラへのオブション、リンカへのオプションなどを マクロとして記述すると、以下のようになる。 Makefileを書く手間は増えるが、第3者に対する情報が増える。

ここで、$@がターゲット、 $^がソースに展開されるマクロ名である。

CFLAGS = -Wall
CC     = gcc $(CFLAGS) 
LFLAGS = -lm

runrun : runrun.c
→$(CC) -o $@ $(LFLAGS) $^

clean:
→rm -rf runrun *.o

複数ファイルの場合

C言語で複数のファイルからプログラムを構成する場合、 単純に書くと、例えば次のようになる。
PRG    = blackjack
SRC    = main.c sub1.c sub2.c sub3.c

CFLAGS = -Wall
LFLAGS = -lm -lX11
CC     =  gcc $(CFLAGS)

$(PRG) : $(SRC)
→$(CC) -o $@ $^ $(LFLAGS)

clean : 
→rm -f $(PRG)

このMakefileでは、 main.c, sub1.c, sub2.c, sub3.c のどれかひとつでも変更されていたら、 実行ファイル blackjack が作成しなおされる。 しかし、 コンパイラがどういう処理を行なっているのかを考えると、 sub1.cだけを変更したときに、sub2.c, sub3.cを再コンパイルするのは無駄である。 (コンピュータが高速になっているので、この余分な処理にかかる時間は ほとんど気にならないことが多いが、それでもやはり 開発規模が大きくなると、この無駄が意外と無視できない。)

複数ファイルのときには、 それぞれのソースファイルからオブジェクトファイルを生成し、 最後にリンクすることで、実行ファイルが作成されている。 したがって、もしオブジェクトファイルが残っているならば、 変更がないオブジェクトファイルの生成を省略できる。 この点を踏まえて、記述されたMakefileは次のようになる。

PRG    = blackjack
OBJ    = main.o sub1.o sub2.o sub3.o

CFLAGS = -g -Wall
LFLAGS = -lm -lX11

CC     =  gcc $(CFLAGS) 

$(PRG) : $(OBJ)
→$(CC) -o $@  $^ $(LFLAGS)

main.o : main.c
→$(CC) -c $^

sub1.o : sub1.c
→$(CC) -c $^

sub2.o : sub2.c
→$(CC) -c $^

sub3.o : sub3.c
→$(CC) -c $^

clean : 
→rm -f $(PRG) $(OBJ)

サフィックス ルール

ここで「〜.o」をつくるところをよく見ると、どのルールでも
$(CC) -c $^
と同じ形になっていることがわかる。

このように、 ターゲットファイル名とソースファイル名の サフィックス(いわゆる拡張子だと思えば良い)のみが異なり、 かつ、サフィックスの関係だけで行なうべきコマンドが決まる場合には、 次のようにまとめて記述することができる。 このようなルールの書き方を「サフィックスルール」と呼ぶ。

PRG    = blackjack
OBJ    = main.o sub1.o sub2.o sub3.o

CFLAGS = -g -Wall
LFLAGS = -lm -lX11

CC     =  gcc $(CFLAGS)

$(PRG) : $(OBJ) 
→$(CC) -o $@ $^ $(LFLAGS)

.c.o:               # ←サフィックスルールによる記述
→$(CC) -c $^       #

clean:
→rm -rf $(PRG) $(OBJ)

このサフィックスルールでは、 (明示的に依存関係が書かれていないので) ターゲットファイル名が「なんとか.o」のとき、 ソースファイル名は「なんとか.c」となる。 (依存関係が別に書かれている時には、 そのソースファイル名が用いられる。)

サフィックスルールの場合には、 コマンド中に $@のような 自動変数をよく使うことになる。これ以外にも次のようなものがある。

変数説明
$@ターゲットファイル名
$^すべての依存するファイル名
$<最初の依存するファイル名
$?ターゲットより新しいすべての依存するファイル名
$*サフィックスを除いたターゲット名

ヘッダーファイルの依存関係を反映させる

仮に main.c が sub1.c 内の関数を呼びだし、 sub1.c内の関数が、sub2.c および sub3.c 内の関数を呼び出しているとすると、 各ソースファイルのヘッダには、例えば次のように#include文が 書かれているはずである。


main.c
#include <stdio.h>
#include "sub1.h"

sub1.c
#include <stdio.h>
#include <string.h>
#include "sub2.h"
#include "sub3.h"

sub2.c
#include "sub2.h"

sub3.c
#include "sub3.h"

このような状況では、 もし sub1.h に変更が加えられた場合、main.c をコンパイルしなおす (つまりmain.oを作り直す)必要がある。 しかし、さきほどまでのMakefileにはその依存関係は含まれていない。

ヘッダファイルに関する依存関係を含めた Makefileは次のようになる。 サフィックスルール(.c.o)のところのマクロが、 $^ から $<に変わっていることに注意せよ。

PRG    = blackjack
OBJ    = main.o sub1.o sub2.o sub3.o

CFLAGS = -g -Wall
LFLAGS = -lm -lX11

CC     =  gcc $(CFLAGS)

$(PRG) : $(OBJ) 
→$(CC) -o $@ $^ $(LFLAGS)

.c.o:               # サフィックスルール
→$(CC) -c $<       # コンパイルには依存ファイルの最初のファイルだけを使う。

clean:
→rm -rf $(PRG) $(OBJ)

# 実際の依存関係はここから
main.o : main.c sub1.h      
sub1.o : sub1.c sub1.h sub2.h sub3.h
sub2.o : sub2.c sub2.h
sub3.o : sub3.c sub3.h
これが一応の完成形である。

Makefileを楽に書きたい

ところで、ファイルが多くなってくると、 Makefileの依存関係の部分を人が間違わずに書くことは難しくなってくる。 依存関係は、ソースファイル中の#inlcudeの情報で 判断することができる。 gccには、この依存関係を生成してくれる機能がある。

gcc -MM main.c sub1.c sub2.c sub3.c
とすると、次のようなものが標準出力に出力される。
main.o : main.c sub1.h      
sub1.o : sub1.c sub1.h sub2.h sub3.h
sub2.o : sub2.c sub2.h
sub3.o : sub3.c sub3.h

この出力をMakefileに取り込むには次のようにする。

  1. あらかじめ、Makefileとして、以下のようなものを作っておく。
    PRG    = blackjack
    OBJ    = main.o sub1.o sub2.o sub3.o
    
    CFLAGS = -g -Wall
    LFLAGS = -lm -lX11
    
    CC     =  gcc $(CFLAGS)
    
    $(PRG) : $(OBJ) 
    →$(CC) -o $@ $^ $(LFLAGS)
    
    .c.o:
    →$(CC) -c $<
    
    clean:
    →rm -rf $(PRG) $(OBJ)
    
  2. シェルのリダイレクションを利用して、 このファイルの最後尾に依存関係をくっつける。
    gcc -MM main.c sub1.c sub2.c sub3.c >> Makefile