2020/09/26: zennに改訂版を執筆しました。
c/c++のコンパイルの定義を記述するMakefileの書き方を説明します。
初心者向けにわかりやすく書くつもりです。Makefileを業務で扱う必要があるが、書き方がわからない人はぜひ参考にしてください。
そもそもMakefileって?
c/c++のコンパイルに必要なコマンド、ファイル、ファイルの参照パス、オプション、依存関係などを書き込んだファイルです。
このファイルを使って、makeコマンドを実行することで、コンパイルを実施します。
一度、Makefileをつくってしまえば、コンパイルの手間を大幅に減らすことができます。
また、差分コンパイルが可能で、ファイル修正後再コンパイルをする時間を短縮するメリットもあります。
Makefileのサンプル
ソースコードSample.cppをコンパイルし、実行ファイルを生成するためのMakeifleのサンプルを書いてみます。
# (1)コンパイラ
CC = g++
# (2)コンパイルオプション
CFLAGS =
# (3)実行ファイル名
TARGET = Sample
# (4)コンパイル対象のソースコード
SRCS = Sample.cpp
# (5)オブジェクトファイル名
OBJS = $(SRCS:.cpp=.o)
# (6)インクルードファイルのあるディレクトリパス
INCDIR = -I../inc
# (7)ライブラリファイルのあるディレクトリパス
LIBDIR =
# (8)追加するライブラリファイル
LIBS =
# (9)ターゲットファイル生成
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LIBDIR) $(LIBS)
# (10)オブジェクトファイル生成
$(OBJS): $(SRCS)
$(CC) $(CFLAGS) $(INCDIR) -c $(SRCS)
# (11)"make all"で make cleanとmakeを同時に実施。
all: clean $(OBJS) $(TARGET)
# (12).oファイル、実行ファイル、.dファイルを削除
clean:
-rm -f $(OBJS) $(TARGET) *.d
何が便利か?
Makefileの何が便利かがピンと来るように、「Makeifleを使わないコンパイル」と「Makeifleを使ったコンパイル」の2つを比較してみます。
前提として、ディレクトリ階層は以下になります。
─HogeProject
│ ├─src
│ │ ├─Sample.cpp
│ │ ├─Makefile
│ ├─inc
1. Makeifleを使わないコンパイル。
まずは、Makefileを使わないコンパイルをやってみます。
コンパイル対象のソースであるSanple.cppがある階層で以下の通りコマンドを実行する。
$ g++ -std=c++0x -I../inc -c Sample.cpp; g++ -o Sample Sample.o
プログラムの規模が小さいので、それほど面倒ではありませんが、それでも手打ちでコンパイルするのは、かなりの手間です。
2. Makeifleを使ったコンパイル
同じことをMakeifleを使ってやってみます。
Sanple.cppと同階層にMakeifileを配置して、以下の通りコマンドを実行しています。
$ make
以上!!
Makefileは実際書くのは、結構面倒ですが、一度書いてしまえばコンパイルの手間が圧倒的に減りミスも少なくなります。特に、大きなプログラムであれば有るほどこの恩恵は大きいです。
また、通常の業務開発はチームで開発することがほとんどですが、Makefileを使えば各担当者間でコンパイルルールの統一ができる点も大きいです。
Makfileの書き方
いよいよ、本題であるMakfileの書き方の説明に入ります。
[Makefileのサンプル](# Makefileのサンプル)で書いたMakfileの書き方を説明します。
私は、Makefileは3つのブロックに分けて考えると理解しやすいと考えています。
- Makeに必要な情報
- ターゲットの生成
- Make実行オプション
各ブロックにわけて説明していきます。
1. Makeに必要な情報
Makeに必要な情報を記載してきます。コンパイルの下準備的な感じです。サンプルの1-19行目に当たります。
(1)コンパイラ
使用するコンパイラを記載します。今回は、gccコンパイラを使っています。
ただし、C++をコンパイルするため表記はg++です。
(2)コンパイルオプション
コンパイルに使用するオプションです。
追加することで「使用できる機能」や「コンパイル時に出力される警告」などを制御することができます。オプションの種類やより詳しい説明は、次回以降とします。
(3)実行ファイル名
実行ファイルの名前をつけます。好きな名前をつけてください。
(4)コンパイル対象のソースコード
コンパイル対象のソースコードを指定します。
今回は、1ファイルしか指定していませんが、ソースファイルが複数ある場合は複数指定してください。複数指定する場合の例。
SRCS = hoge1.cpp
SRCS += hoge2.cpp
SRCS += hoge3.cpp
(5)オブジェクトファイル名
オブジェクトファイルの名称を定義します。
ソースファイル名と同一のオブジェクトファイルを作ることが多いです。
(6)インクルードファイルのあるディレクトリパス
参照するインクルードファイルが存在するパスを指定します。
インクルードファイル名は記載不要です。
(7)ライブラリファイルのあるディレクトリパス
OSSなどライブラリを指定する必要がある場合は、そのライブラリが存在するパスを指定します。
今回は、省略しています。
(8)追加するライブラリファイル
(7)同様、省略します。
2. ターゲットの生成
ここで言うターゲットとは「TARGET」と「OBJS」のことを指します。
「1.」で示した情報を形成して、オブジェクトファイルと実行ファイルの生成ルールを記述する箇所になります。
サンプルの21-27行目に当たります。
(9)ターゲットファイル生成
以下の2行の構成でターゲットファイルの生成ルールを記述しています。
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LIBDIR) $(LIBS)
1行目は、$(TARGET): $(OBJS)と記載して、「TARGET」が「OBJS」に依存することを示しています。
2行目は、コマンド行と言います。リンク対象のライブラリを指定します。
今回は、リンクするライブラリは存在しないため、「OBJS」との依存関係のみ管理することになります。
(10)オブジェクトファイル生成
(9)で依存する「OBJS」の生成ルールです。オブジェクトファイルは、ソースと機械語の中間ファイルに当たるため、ソースコードに依存します。基本的な記載ルールは、(9)と同じです。
3. Make実行オプション
実は、「1.」、「2.」だけでコンパイルはできるのですが、makeのルールをオプションとして自由に定義することが可能です。
こうすることで、より効率よくコンパイルができるようになります。
サンプルでは、よく指定される、2つ例を示します。
サンプルの29-33行目に当たります。
(11)"make all"で make cleanとmakeを同時に実施。
タイトルのとおり、(12)のcleanとmakeコマンドを同時に実行してくれるallオプションを作りました。
make allとうつだけで、前のビルド時に生成したファイルを削除したうえで、再ビルドを実行してくれます。
(12).oファイル、実行ファイル、.dファイルを削除
ビルドで生成したファイルを削除します。
削除対象は、オブジェクトファイル、実行ファイル、そしてデフォルトで生成される依存関係ファイルを削除してくれます。
終わりに
私自身、新卒でMakefileを扱うことになったとき、全くわからずすごく苦労しました。
1つ1つの定義の意味がわからないのもありましたが、「Makefileがなぜ必要で(何が便利で)」というのがわからなず、もやもやしていました。そういった自分自身の経験もあり、この記事を書くことにしました。
ちなみに、私がC++を扱っていたのでは、2017年頃までで、それからは随分離れています。
本記事も過去の記憶を思い出しつつ書いている部分があるので、誤りや不明点等あれば、ご連絡よろしくお願いします。