C言語のMakeの使い方を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私が解説していきます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
C言語はPython,Ruby,HTML/CSS/JS/PHPのようなアプリケーション系の言語と比較して難易度が高いですよね.
私がC言語を習い始めた時は,デバッグがうまくいかなくて何度も挫折しそうになり,その度に開発ツールに助けられました.
そんなあなたに元東大教員がおすすめするC言語の開発ツールとしてMakeを紹介します.
目次
Make
MakeはGNUが開発した代表的なビルド自動化ツールで,C言語だけでなくプログラミング言語全般で利用できます.
Makeの目的は,もともと複数のファイルが複雑な依存関係をしている際のコンパイル作業を軽減することです.
つまり,Makeを効果的に利用するとソフトウェアの開発時間を大幅に削減できます.
ファイルの依存関係をMakefileと呼ばれるファイルに記述すれば,Makeは依存関係をたどって必要最低限の作業で分割コンパイル等を行うことが可能になります.
そのために,Makeは依存関係のあるファイルの更新日時をチェックします.
あるファイルを修正したとき,コンパイル回数を必要最小限に抑えるように制御(コンパイル)しようとします.
Makeは,Linuxカーネルを含む多くのC言語で開発されたソフトウェアで利用されています.
LinuxカーネルはGitでバージョン管理されていますが,GitもC言語で開発されたソフトウェアでMakeでビルドできます.
このようにMakeはC言語の開発ツールでよく使われていますので,使いこなせるようにしましょう.
LInuxカーネルを学びたいあなたはこちらからどうぞ.
Makeの実行
Makeを実行するためには,カレントディレクトリにMakefileという名前のファイルを用意し,Makefileに依存関係を記述します.
Makefileのファイル名は,先頭の文字が小文字のmakefileでも構いませんが,通常はMakefileという先頭の文字が大文字のファイル名にします.
この理由は,Linux上で開発するC言語のファイル名は通常は先頭が小文字なので,大文字から始まるMakefileのファイル名にすると見つけやすくするためだと思われます.
※lsコマンドを実行する場合,先頭が大文字のファイル名が先に表示されます.
そして,以下のコマンドでMakeを実行できます.
1 |
$ make |
Makefileという名前以外のファイルをMakefileとして指定する際には-fオプションを利用して任意のファイル名のMakefileを指定できます.
1 |
$ make -f mymakefie |
また,大規模な開発でMakeを複数のジョブで並列コンパイルして高速に処理(並列Make)したい場合,-jオプションを利用します.
-jオプションの直後に並列処理したいジョブ数(以下の例では4)を指定します.
1 |
$ make -j4 |
Makefileの記述
Makefileの基本フォーマットは,以下のようになります.
コマンドの前は必ずタブでなければならないことに注意して下さい.
1 2 |
ターゲットファイル: 依存ファイル(複数可) コマンド |
もしXX行目のコマンドの前にタブではなくスペースを入れた場合,以下のエラーが発生します.
1 2 |
$ make Makefile:XX: *** missing separator. Stop. |
Makeは,ターゲットファイルと依存ファイルの日時を比較し,依存ファイルの日時の方がターゲットファイルの日時よりも新しければコマンドを実行します.
コマンドには,ターゲットファイルを生成するための実行コマンドを記述します.
以下のファイル一式は,こちらからダウンロードできます.
1 2 3 4 5 6 7 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #define square(x) ((x) * (x)) extern int func_a(int x); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include "m1-1.h" int func_a(int x) { int y; y = square(x) + x + 1; return y; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include "m1-1.h" int main(void) { int x, y; printf("Please input x = "); scanf("%d", &x); y = func_a(x); printf("x = %d, y = %d\n", x, y); return 0; } |
ここで重要なのは,m1-1.hはm1-1.cとm1-2.cの両方でインクルードされていることです.
つまり,m1-1.cとm1-2.cはm1-1.hの依存しています.
これらのファイルを分割コンパイルするシンプルなMakefileは以下のM1-1a.makになります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# # Makefile # # Hiroyuki Chishiro # m1-1: m1-1.o m1-2.o gcc -o m1-1 m1-1.o m1-2.o m1-1.o: m1-1.c gcc -c m1-1.c m1-2.o: m1-2.c gcc -c m1-2.c m1-1.c: m1-1.h m1-2.c: m1-1.h |
M1-1a.makでMakeを実行すると以下になります.
1 2 3 4 |
$ make -f M1-1a.mak gcc -c m1-1.c gcc -c m1-2.c gcc -o m1-1 m1-1.o m1-2.o |
実行ファイルm1-1は,m1-1.oとm1-2.oに依存しています.
つまり,どちらかのファイルが変更されたら,再コンパイル(この場合はリンク)する必要があります.
具体的には,m1-1.o,m1-2.oとm1-1の日時を比較し,m1-1.oかm1-2.oの日時がm1-1よりも新しければMakeは以下を実行します.
1 |
gcc -o m1-1 m1-1.o m1-2.o |
さらに,m1-1.oはm1-1.cに依存し,m1-2.oはm1-2.cに依存します.
m1-1.oの日時とm1-1.cの日時を比較し,m1-1.cの日時が新しければMakeは以下を実行します.
m1-2.oとm1-2.cの関係も同様です.
1 |
gcc -c m1-1.c |
m1-1.cとm1-2.cはともにm1-1.hに依存しています.
この場合,m1-1.hはコンパイル時にインクルードされるので,コマンドには何も書かなくてもよいです.
Makeは,m1-1.hが変更されると(日時が新しくなると),m1-1.cやm1-2.cが変更されたとみなします.
したがって,m1-1.oとm1-2.oがそれぞれコンパイルされ,次に依存関係からm1-1がリンクされます.
また,このMakefileはM1-1b.makのように書くこともできます.
この例では,m1-1.oはm1-1.cとm1-1.hの両方に依存しているというように記述しているので,m1-1.cかm1-1.hが変更される(日時が新しくなる)と,コンパイルします.m1-2.oも同様です.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# # Makefile # # Hiroyuki Chishiro # m1-1: m1-1.o m1-2.o gcc -o m1-1 m1-1.o m1-2.o m1-1.o: m1-1.c m1-1.h gcc -c m1-1.c m1-2.o: m1-2.c m1-1.h gcc -c m1-2.c |
M1-1b.makでMakeを実行すると以下になります.
※m1-1,m1-1.o,m1-2.oを手動で削除してから実行して下さい.
1 2 3 4 |
$ make -f M1-1b.mak gcc -c m1-1.c gcc -c m1-2.c gcc -o m1-1 m1-1.o m1-2.o |
Makefileのサフィックスルール
先述したMakefile(M1-1a.makやM1-1b.mak)の記述には似たような記述が多くあり,ファイルが多くなると記述が大変になります.
実際にMakeは,このような場合(ファイル数が数百以上)にとても役に立ちます.
Makeにはサフィックスルール(拡張子.cや.o)により簡単に記述できます.
サフィックスルールを利用して書き直したMakefileは,以下のM1-1c.makのようになります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# # Makefile # # Hiroyuki Chishiro # m1-1: m1-1.o m1-2.o gcc -o m1-1 m1-1.o m1-2.o .c.o: gcc -c $< m1-1.o: m1-1.h m1-2.o: m1-1.h |
ここでは,以下のようなサフィックスルールが利用されています.
1 2 |
.c.o: gcc -c $< |
この意味は,サフィックス.cのファイルからサフィックス.oのファイルを作成するためには以下を実行するということです.
1 |
gcc -c $< |
また,$<はMakeのマクロ(自動変数)であり,具体的にはターゲットが依存ファイルの最初の1つに展開されます.
したがって,上記のように記述したサフィックスルールによりMakeは*.oというファイルが必要であり,そのファイルを作成するためには*.cというファイルが必要です.
そして,Makeは*.cから*.oを作成するためには上記のコマンド(gcc -c $<)を利用します.
M1-1c.makでMakeを実行すると以下になります.
※m1-1,m1-1.o,m1-2.oを手動で削除してから実行して下さい.
1 2 3 4 |
$ make -f M1-1c.mak gcc -c m1-1.c gcc -c m1-2.c gcc -o m1-1 m1-1.o m1-2.o |
その他のマクロ(自動変数)には,主に下表のものがあります.
マクロ | 意味 |
---|---|
$@ | ターゲットファイル名 |
$< | 最初の依存ファイル名 |
$~ | 全ての依存ファイル名 |
$* | サフィックスを除いたターゲットファイル名 |
$? | ターゲットファイルより新しい全ての依存ファイル名 |
Makefileのマクロ
Makefileは,ユーザ定義のマクロを利用して記述できます.
先述したMakefileをユーザ定義マクロで書き直したものがM1-1d.makです.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# # Makefile # # Hiroyuki Chishiro # CC = gcc OBJS = m1-1.o m1-2.o m1-1: $(OBJS) gcc -o $@ $(OBJS) .c.o: $(CC) -c $< m1-1.o: m1-1.h m1-2.o: m1-1.h |
ユーザ定義マクロは以下のように記述できます.
1 |
identifier = token-sequence |
M1-1d.makの場合は,以下のように,CCとOBJSというマクロを定義しています.
1 2 |
CC = gcc OBJS = m1-1.o m1-2.o |
定義したマクロを利用するためには,$マークを付けてカッコ()で囲めば参照できます.
1 2 |
m1-1: $(OBJS) gcc -o $@ $(OBJS) |
例えば,上記のマクロは以下のように展開されます.
1 2 |
m1-1: m1-1.o m1-2.o gcc -o m1-1. m1-1.o m1-2.o |
M1-1d.makでMakeを実行すると以下になります.
※m1-1,m1-1.o,m1-2.oを手動で削除してから実行して下さい.
1 2 3 4 |
$ make -f M1-1d.mak gcc -c m1-1.c gcc -c m1-2.c gcc -o m1-1 m1-1.o m1-2.o |
Makefileでサフィックスルールとマクロの利用
Makefileをサフィックスルールとマクロを利用したものが,M1-1e.makになります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# # Makefile # # Hiroyuki Chishiro # CC = gcc CFLAGS = -Wall LDFLAGS = TARGET = m1-1 SRCS = m1-1.c m1-2.c OBJS = $(SRCS:.c=.o) RM = rm -f all: $(TARGET) $(TARGET): $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ .c.o: $(CC) $(CFLAGS) -c $< m1-1.o: m1-1.h m1-2.o: m1-1.h clean: $(RM) $(OBJS) $(TARGET) clean_obj: $(RM) $(OBJS) clean_target: $(RM) $(TARGET) |
OBJSマクロで以下のように書くと,オブジェクトのファイル名はSRCSマクロの.cを.oに変換したものになります.
SRCSがm1-1.cとm1-2.cなので,OBJSはm1-1.oとm1-2.oになります.
1 |
OBJS = $(SRCS:.c=.o) |
M1-1e.mak内では,以下のようにコンパイルオプションを指定するCFLAGSマクロを定義しています.
1 |
CFLAGS = -Wall |
同時にリンクオプションのLDFLAGSマクロも定義していますが,この例では何も指定していません.
例えば,数学ライブラリ(libm.so)をリンクしたい場合は以下のように指定します.
1 |
LDFLAGS = -lm |
Makeは,ターゲット名を指定すると,そのターゲットを実行します.
例えば,以下のように実行すると,allというターゲットが実行されます.
1 |
$ make -f M1-1e.mak all |
Makeのデフォルトのターゲット名はallなので,allは省略でき,上記は以下と同じ実行になります.
1 |
$ make -f M1-1e.mak |
また,以下のように実行するとターゲットファイル(m1-1)とオブジェクトファイル(*.o)とを削除します.
1 |
$ make -f M1-1e.mak clean |
オブジェクトファイルのみ削除したい場合は以下のように実行します.
1 |
$ make -f M1-1e.mak clean_obj |
ターゲットファイルのみ削除したい場合は以下のように実行します.
1 |
$ make -f M1-1e.mak clean_target |
このようにMakefileを適切に記述しておくと,複雑な依存関係があるような大規模プログラムの開発が楽になります.
M1-1e.makでMakeを実行すると以下になります.
1行目の「make -f M1-1e.mak clean」でターゲットファイルとオブジェクトファイルを削除します.
3行目の「make -f M1-1e.mak」でビルドします.
1 2 3 4 5 6 |
$ make -f M1-1e.mak clean rm -f m1-1.o m1-2.o m1-1 $ make -f M1-1e.mak gcc -Wall -c m1-1.c gcc -Wall -c m1-2.c gcc -Wall -o m1-1 m1-1.o m1-2.o |
Makeの疑似ターゲット「PHONY」と依存ファイル「DEPS」
先述したM1-1e.makで以下のように疑似ターゲット「PHONY」 と依存ファイル「DEPS」を設定したM1-1f.makは以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# # Makefile # # Hiroyuki Chishiro # CC = gcc CFLAGS = -Wall LDFLAGS = TARGET = m1-1 SRCS = m1-1.c m1-2.c OBJS = $(SRCS:.c=.o) DEPS = $(SRCS:.c=.d) RM = rm -f .PHONY: all clean clean_obj clean_target all: $(TARGET) $(TARGET): $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ .c.o: $(CC) $(CFLAGS) -c -MMD -MP -MF $(@:%.o=%.d) $< m1-1.o: m1-1.h m1-2.o: m1-1.h clean: $(RM) $(OBJS) $(DEPS) $(TARGET) clean_obj: $(RM) $(OBJS) clean_target: $(RM) $(TARGET) ifneq ($(DEPS),) -include $(DEPS) endif |
PHONYを設定すると同じファイルがある場合でも適切に動作します.
1 |
.PHONY: all clean clean_obj clean_target |
また,DEPSマクロで依存ファイルを以下のように定義します.
1 |
DEPS = $(SRCS:.c=.d) |
コンパイルオプションは以下のように追加します.
1 2 |
.c.o: $(CC) $(CFLAGS) -c -MMD -MP -MF $(@:%.o=%.d) $< |
また,ファイルの最後に以下のように記述します.
1 2 3 |
ifneq ($(DEPS),) -include $(DEPS) endif |
M1-1f.makでMakeを実行すると以下になります.
1 2 3 4 5 6 |
$ make -f M1-1f.mak clean rm -f m1-1.o m1-2.o m1-1.d m1-2.d m1-1 $ make -f M1-1f.mak gcc -Wall -c -MMD -MP -MF m1-1.d m1-1.c gcc -Wall -c -MMD -MP -MF m1-2.d m1-2.c gcc -Wall -o m1-1 m1-1.o m1-2.o |
DEPSマクロにより作成されたm1-1.dとm1-2.dファイルの中身は以下になります.
オブジェクトファイルとヘッダファイル毎にそれぞれに依存する.c/.hファイルが設定されていることがわかります.
依存しているファイルが更新された場合にのみ再コンパイルする設定になるため,開発効率を向上させることが可能になります.
1 2 3 4 5 6 |
$ cat m1-1.d m1-1.o: m1-1.c m1-1.h m1-1.h: $ cat m1-2.d m1-2.o: m1-2.c m1-1.h m1-1.h: |
Makeの実例
Makeの実例を学びたいあなたはこちらからどうぞ.
CMake
CMakeは,コンパイラに依存しないビルド自動化のためのツールで,LinuxやWindowsを含む様々なOSで動作させることができます.
実際のビルドでは,makeやVisual StudioのようなOSネイティブのビルド環境が利用されます.
CMakeの使い方は以下の記事がわかりやすいです.
まとめ
C言語でMakeの使い方を紹介しました.
Makeを使いこなすことで,Linuxカーネルのビルドがどのように実行されるのかが理解できます.
Makeを深く理解したいあなたは,オライリー・ジャパン発行の書籍「GNU Make 第3版」を読みましょう.
この本は有料ですが,各章のPDFは無料で読めますので是非読みましょう.
各章のPDFだと目次の各章へのリンクがなくて読みにくいと思いますので,こちらから一つのPDFになったものをダウンロードして読むことをおすすめします.
英語ができる方は「Makeの公式マニュアル」を読むことをおすすめします.
All-in-Oneのビルドツール「SCons」を知りたいあなたはこちらからどうぞ.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!