Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

Makefileの書き方

利用するケース

@yutakikuchi_です。
C/C++を書いた時に複数ファイルから実行ファイルを生成するときやライブラリをIncludeする場合コンパイルのオプションが複雑になります。複雑なオプションを毎回コマンドラインで入力するのではなく、Makefileというコンパイルのオプションルールを記載してmake/gmakeコマンドにて実行ファイルを生成すると便利です。今回はMakefileの簡単なルールについて紹介します。

Makefileの基本

基本ルール

C++ソースのコンパイルにはg++を利用します。例えばhello.cppというファイルをコンパイルする場合は$ g++ hello.cpp -o helloと実行するとhelloという実行ファイルが生成されます。これをMakefileを使って書くと次のようになります。Makefileを作成したらgmakeとコマンドを実行するだけです。

hello: hello.cpp  #ターゲット: 依存ファイル
    g++ -Wall hello.cpp -o hello  #実行コマンド
clean:
    rm -f *.o hello

書式を簡単に説明すると、1行目に生成したいターゲットファイル名: 依存ファイル、2行目に生成するための実行コマンドを記載します。実行コマンドの先頭にはTabを入力する必要があります。viなどではControl-V、Tabとして入力すると識別されると思います。Tabではなくspaceを入れてしまうと「Makefile:2: *** 分離記号を欠いています. 中止.」とエラーが出力されてしまうので注意が必要です。
上のMakefileをgmakeコマンドで実行するとhelloという実行ファイルが生成されます。生成された実行ファイルを消去したい場合はgmake cleanと実行するとrm -f *.o helloの箇所が実行されます。g++の-Wallオプションですが、全ての警告オプションを結合してくれるもので一番厳密に文法をチェックします。

$ ls 
drwxr-xr-x 2 yuta yuta 4096  6月 30 11:40 .
drwxr-xr-x 6 yuta yuta 4096  6月 30 11:20 ..
-rw-r--r-- 1 yuta yuta   71  6月 30 11:32 Makefile
-rw-r--r-- 1 yuta yuta   91  6月 30 11:17 hello.cpp
$ gmake
g++ -Wall hello.cpp -o hello
$ ls
drwxr-xr-x 2 yuta yuta 4096  6月 30 11:40 .
drwxr-xr-x 6 yuta yuta 4096  6月 30 11:20 ..
-rw-r--r-- 1 yuta yuta   71  6月 30 11:32 Makefile
-rwxr-xr-x 1 yuta yuta 8548  6月 30 11:40 hello
-rw-r--r-- 1 yuta yuta   91  6月 30 11:17 hello.cpp
$ gmake clean
rm -f *.o hello
$ ls
drwxr-xr-x 2 yuta yuta 4096  6月 30 11:41 .
drwxr-xr-x 6 yuta yuta 4096  6月 30 11:20 ..
-rw-r--r-- 1 yuta yuta   71  6月 30 11:32 Makefile
-rw-r--r-- 1 yuta yuta   91  6月 30 11:17 hello.cpp
複数ファイルの結合

Makefileのターゲットファイル名:依存ファイルという書式の依存ファイル名を複数指定してみます。新たにprint.cppというファイルを定義して以下のようなMakefileに書き換えます。main処理をhello.cppに、mainの中で利用したい関数をprint.cppに記述しています。

hello: hello.cpp print.cpp
	g++ -Wall hello.cpp print.cpp -o hello
clean:
	rm -f *.o hello

実は上の書き方はあまり推奨されません。というのもコンパイルの実行が毎回両方のファイルに適用されてしまいます。片方だけupdateを掛けたい時もあり、次の例のように各生成ファイル毎に記述を分離するのが一般的です。gmakeを実行すると修正された各cppからオブジェクトコードを生成し、最後に実行ファイルにまとめます。

hello: hello.o print.o
	g++ -Wall -o hello hello.o print.o 
print.o: print.cpp
	g++ -Wall -c print.cpp 
hello.o: hello.cpp
	g++ -Wall -c hello.cpp
clean:
	rm -f *.o hello
$ gmake
g++ -Wall -c hello.cpp
g++ -Wall -c print.cpp 
g++ -Wall -o hello hello.o print.o 
シンボル

複雑なコンパイルオプションやファイルをincludeする指定などはmakeのシンボルを利用します。良く利用するシンボル一覧は次のものになります。またシンボルの定義等で利用する演算子についても一覧にまとめます。

シンボル 説明 default
CC Cコンパイルコマンド cc
CXX C++コンパイルコマンド g++
CFLAGS Cコンパイルオプション 無し
CXXFLAGS C++コンパイルオプション 無し
CPPFLAGS C++プリプロセッサ用オプション 無し
LDFLAGS ldというリンクを呼び出すコマンドのリンクオプション 無し
INCLUDES includeするheaderのディレクトリを指定する 無し
LIBS 利用するライブラリを指定する 無し
TARGET 生成するターゲットファイル名 無し
SRCS ターゲットファイルを生成するために利用するソースコード 無し
OBJS ターゲットファイルを生成するために利用するオブジェクトファイル 無し
RM ファイル削除コマンド rm -f
演算子 説明
:= 右辺を即時に評価
= 使用される度に右辺を再評価
?= 条件代入。値を持ってない場合に利用
+= 追加代入


特に重要なのがCXXFLAGSとLDFLAGSかと思います。CXXFLAGSにはエラーオプション、CPU情報、最適化レベル、コンパイル高速化オプションなどが設定できます。LDFLAGSについてはダイナミックリンクを貼る設定が定義可能です。
その他書き方の注意点としてINCLUDES、LIBSなどでディレクトリを指定する場合は-I,-Lといったハイフンと大文字のIとLを連結します。さらにLIBSでは指定したディレクトリ以下のどのライブラリを使うかも指定しますが、その際は-lといったハイフンと小文字のLを利用します。以下に簡単な例を記載します。

INCLUDES = -I/usr/local/include -I../libmisc -I.
# /usr/loca/include, ../libmisc, . の3つのディレクトリからincludeする。
LIBS	= -L../libmisc -lmisc -lX11 -lGL -lGLU -lm
# ../libmiscディレクトリの misc, X11, GL, GLU, mをライブラリとして指定する。
自動変数

Makefile中に記号の組み合わせで変数を表すものがあります。以下に一覧で示します。

変数 説明
$@ ターゲットファイル名
$< 最初の依存ファイル名
$? ターゲットより新しい全ての依存ファイル名
$^ 全ての依存ファイル名
$+ Makefileと同じ順番の依存ファイル名
$* suffixを除いたターゲット名
$% アーカイブだった時のターゲットメンバ名
シンボルと自動変数を使用したMakefile

上で紹介したシンボルと自動変数を使ったMakefileの例を以下に示します。CXXFLAGSで指定している-O2と-pipeはコンパイル最適化レベルとコンパイルを高速にするオプションです。-O0で最適化をしないもので、通常は-O2を指定するようです。その他CXXFLAGSには-m32や-march=i386を指定する事があります。これは64bit環境でも-m32を指定すると32bit用にコンパイル、-marchでどのCPUに向けたコンパイルなのか定義できます。下ではgmakeの実行例も記載します。

CXX = g++
TARGET = hello
CXXFLAGS = -Wall -O2 -pipe
SRCS = hello.cpp print.cpp
OBJS := $(SRCS:.cpp=.o) #SRCSの各ファイルのサフィックスの.cppを.oに変換

$(TARGET): $(OBJS)
	$(CXX) -o $@ $(OBJS)

clean:
	rm -f $(TARGET) $(OBJS)
$ ls 
drwxr-xr-x 2 yuta yuta 4096  7月  1 12:24 .
drwxr-xr-x 6 yuta yuta 4096  6月 30 11:20 ..
-rw-r--r-- 1 yuta yuta  178  7月  1 12:18 Makefile
-rw-r--r-- 1 yuta yuta   83  6月 30 12:00 hello.cpp
-rw-r--r-- 1 yuta yuta  107  6月 30 12:15 print.cpp
$ gmake 
g++ -Wall -O2 -pipe   -c -o hello.o hello.cpp
g++ -Wall -O2 -pipe   -c -o print.o print.cpp
g++ -o hello hello.o print.o