目次
C++やMakefileを活用した開発環境整備について学びながら記事を書いています!
Makefileの基本的な構造
makeコマンドを使えば、Makefileにあらかじめ記述しておいた手順にしたがって、C/C++などのソースファイルから実行ファイルを自動で生成できます。
処理名: 依存するファイルあるいは処理名
[タブひとつ]実行されるコマンド
Makefile
Sample: sample.cpp
g++ -o sample sample.cpp
sample.cpp
#include <iostream>
using namespace std;
int main() {
cout << "This is a sample." << endl;
return 0;
}
実行例
$ make
g++ -o sample sample.cpp
$ ./sample
This is a sample.
複数の処理
処理は複数記述できます。makeコマンドの引数を指定しなければ一番上の処理が実行されます。
Makefile
Sample1: sample1.cpp
g++ -o sample1 sample1.cpp
Sample2: sample2.cpp
g++ -o sample2 sample2.cpp
sample1.cpp
#include <iostream>
using namespace std;
int main() {
cout << "This is Sample1." << endl;
return 0;
}
sample2.cpp
#include <iostream>
using namespace std;
int main() {
cout << "This is Sample2." << endl;
return 0;
}
Sample2の処理を実行したければ
$ make Sample2
とします。
変数の利用
Makefile
CC = g++
CFLAGS = -g -Wall
Sample1: sample1.cpp
$(CC) $(CFLAGS) -o sample1 sample1.cpp
Sample2: sample2.cpp
$(CC) $(CFLAGS) -o sample2 sample2.cpp
処理をネスト
処理をネストさせることができます。一番上にALLという名称の処理を用意して、依存する処理名にSample1とSample2を半角スペースで区切って指定します。すると、ALLの処理を実行すると再帰的にSample1,Sample2の処理も実行されます。
Makefile
CC = g++
CFLAGS = -g -Wall
ALL: Sample1 Sample2
Sample1: sample1.cpp
$(CC) $(CFLAGS) -o sample1 sample1.cpp
Sample2: sample2.cpp
$(CC) $(CFLAGS) -o sample2 sample2.cpp
ヘッダファイル更新を検知
ヘッダファイルが更新された場合にも処理が実行されるようにするためには、依存ファイルを記述する箇所にヘッダファイルを追記します。
Makefile
CC = g++
CFLAGS = -g -Wall
ALL: Sample1 Sample2
Sample1: sample1.cpp sample1.h
$(CC) $(CFLAGS) -o sample1 sample1.cpp
Sample2: sample2.cpp sample2.h
$(CC) $(CFLAGS) -o sample2 sample2.cpp
分割コンパイル
処理のネスト関係を利用すると、分割コンパイル (オブジェクトファイル main.o
と sub.o
を -c
フラグで別々に生成してから、ALL の処理でリンク) もできます。
Makefile
CC = g++
CFLAGS = -g -Wall
ALL: main.o sub.o
$(CC) $(CFLAGS) -o main main.o sub.o
main.o: main.cpp
$(CC) $(CFLAGS) -o main.o -c main.cpp
sub.o: sub.cpp sub.h
$(CC) $(CFLAGS) -o sub.o -c sub.cpp
main.cpp
#include <iostream>
#include "sub.h"
using namespace std;
int main() {
cout << sub::str << endl;
return 0;
}
sub.h
namespace sub {
extern char str[32];
}
sub.cpp
#include "sub.h"
namespace sub {
char str[32] = "Externally defined string.";
}
実行例
$ make
g++ -g -Wall -o main.o -c main.cpp
g++ -g -Wall -o sub.o -c sub.cpp
g++ -g -Wall -o main main.o sub.o
$ ./main
Externally defined string.
特殊変数
$@
および $<
という特殊変数を用いると Makefile 内の共通部分の記述を省略できます。
Makefile
CC = g++
CFLAGS = -g -Wall
ALL: main.o sub.o
$(CC) $(CFLAGS) -o main main.o sub.o
main.o: main.cpp
$(CC) $(CFLAGS) -o $@ -c $<
sub.o: sub.cpp
$(CC) $(CFLAGS) -o $@ -c $<
sub.o: sub.h
$@
には処理名が入っており、$<
には依存するファイルあるいは処理名のうち更新されたものが入っています。sub.hだけ更新される場合があやしいので、sub.oを二つに分けてみました。
共通化 記法1
どの処理名よりも先に.cpp.oという処理を記述すると、依存ファイルがcppで処理名がoの処理を共通化させることができます。
Makefile
CC = g++
CFLAGS = -g -Wall
.cpp.o:
$(CC) $(CFLAGS) -o $@ -c $<
ALL: main.o sub.o
$(CC) $(CFLAGS) -o main main.o sub.o
sub.o: sub.h
共通化 記法2 (GNU make限定)
.cpp.oを用いない別の記述も可能です。処理名がワイルドカード%.oで一致した場合、依存ファイル%.cppの%に%.oの.oを除いた部分が代入されます。
CC = g++
CFLAGS = -g -Wall
%.o: %.cpp
$(CC) $(CFLAGS) -o $@ -c $<
ALL: main.o sub.o
$(CC) $(CFLAGS) -o main main.o sub.o
sub.o: sub.h
ワイルドカード (GNU make限定)
拡張子を置換したリストを生成するためにもワイルドカードを使用できます。
Makefile
CC = g++
CFLAGS = -g -Wall
OBJS = main.o sub.o
DOCS = $(OBJS:%.o=%.txt)
TARGET = main
%.o: %.cpp
$(CC) $(CFLAGS) -o $@ -c $<
ALL: $(OBJS)
$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)
sub.o: sub.h
docs:
touch $(DOCS)
clean:
rm -rf $(OBJS) $(TARGET) $(DOCS)
実行例
$ make
g++ -g -Wall -o main.o -c main.cpp
g++ -g -Wall -o sub.o -c sub.cpp
g++ -g -Wall -o main main.o sub.o
$ make docs
touch main.txt sub.txt
$ make clean
rm -rf main.o sub.o main main.txt sub.txt
その他の話題
コマンド文字列の出力を抑制
@
を付けるとコマンド文字列 が出力されなくなります。
Makefile
hello:
@echo ok
echo ok2
出力例
$ make
ok
echo ok2
ok2
タスク名と同名のファイルが存在する場合を考慮
.PHONY: hello
hello:
@echo ok
echo ok2
同名のファイルが存在しており .PHONY
がない場合、タスクが実行できなくなります。
$ ls
hello Makefile
$ make
make: 'hello' is up to date.
既定で実行されるタスクを指定
.DEFAULT_GOAL
を指定すると、二つ目以降のタスクが既定で実行されるように設定できます。
$ make
help
Makefile
.DEFAULT_GOAL := help
hello:
@echo ok
help:
@echo help
記事の執筆者にステッカーを贈る
有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。
さらに詳しく →Feedbacks
ログインするとコメントを投稿できます。
関連記事
- ダウンキャスト (C++をもう一度)実行時型情報 RTTI #include <iostream> #include <typeinfo> using namespace std; class MyClass { public: virtual ~MyClass() {} // typeid で正しい RTTI // (RunTime Type Information; 実行時型情報) ...
- 競技プログラミングの基本処理チートシート (C++)限られた時間の中で問題を解くために必要となる、競技プログラミングにおける基本的な処理のチートシートです。競プロにおけるメジャー言語 C++ を利用します。その際 C++11 の機能は利用せず C++03 の機能の範囲内で記述します。 頻度高く定期的に開催されるコンテスト AtCoder Codeforces main.cpp #include <iostream>
- 構造体と列挙体 (C++をもう一度)構造体 #include <iostream> using namespace std; struct MyStruct { char charval; int intval; }; void Show(MyStruct* obj) { cout << obj->intval << endl; } int main() { ...
- Valgrind による C/C++ メモリリーク検出JVM メモリリークでは JDK の jstat や jmap で原因を調査できます。C/C++ では valgrind の Memcheck ツールが利用できます。valgrind には複数のツールが含まれており既定のツールが Memcheck です。他のツールを利用する場合は --tool オプションで指定します。 [簡単な利用例](h
- クラスの基本/初期化 (C++をもう一度)構造体のように初期化する (非推奨) #include <iostream> using namespace std; const int MAX_STR = 16; class MyClass { public: int m_integer; char m_str[MAX_STR + 1]; void Show(); }; void MyClass::Show...