[C言語のはじめ方] Part35: Makefileの作成と使用

C言語

はじめに

C言語のプログラミング学習、順調に進んでいますか?😊 ソースコードが1つだけのうちは手動でのコンパイルでも問題ありませんが、プロジェクトが大きくなり、複数のソースファイル(.c)やヘッダーファイル(.h)を扱うようになると、コンパイル作業は非常に煩雑になります。

例えば、以下のようなコマンドを毎回手で入力するのは大変ですよね?

gcc main.c module1.c module2.c -o my_program -Wall -g

さらに、一部のファイルだけを修正した場合でも、毎回すべてのファイルを再コンパイルするのは非効率的です。😭

ここで登場するのが Makefile です! Makefile は、make コマンドというツールと一緒に使うことで、コンパイルやリンクといったビルド作業を自動化してくれる設定ファイルです。Makefileを使うことで、以下のようなメリットがあります。

  • ビルドの自動化: make コマンド一発で、必要なコンパイルやリンクを実行できます。
  • 効率的な再コンパイル: 変更されたファイルとそれに依存するファイルだけを再コンパイルするため、ビルド時間を大幅に短縮できます。
  • プロジェクト管理: コンパイルオプションやファイル間の依存関係をMakefileで一元管理でき、プロジェクトの見通しが良くなります。

このブログ記事では、C言語プロジェクトにおけるMakefileの基本的な作成方法と使い方を学んでいきましょう!

Makefileの基本構文

Makefileの基本は「ルール」の集まりです。各ルールは、基本的に以下の形式で記述されます。

ターゲット: 依存関係リスト
	コマンド

各要素の意味は以下の通りです。

  • ターゲット (Target): 作成したいファイル(例: 実行ファイル、オブジェクトファイル)や、実行したい操作の名前(例: clean)。
  • 依存関係リスト (Dependencies / Prerequisites): ターゲットを作成するために必要なファイルや、他のターゲット。ここに記述されたファイルがターゲットよりも新しい場合、またはターゲットが存在しない場合にコマンドが実行されます。
  • コマンド (Recipe / Commands): ターゲットを作成するために実行されるシェルコマンド。注意点として、コマンド行の先頭は必ずタブ文字でインデントする必要があります。スペースではエラーになるので気をつけましょう!

簡単な例 (単一ファイル)

例えば、hello.c というソースファイルから hello という実行ファイルを作成する場合、Makefileは以下のようになります。

hello: hello.c
	gcc hello.c -o hello

このMakefileがあるディレクトリでターミナルを開き、make コマンドを実行すると、

$ make

と入力します。make はデフォルトでMakefile(またはmakefile, GNUmakefile)という名前のファイルを探し、その中の最初のターゲット(この例では `hello`)を作成しようとします。依存関係である hello.chello より新しいか、hello が存在しなければ、タブでインデントされたコマンド gcc hello.c -o hello が実行されます。

もし特定のターゲットを指定して実行したい場合は、make ターゲット名 のようにします。

$ make hello

変数を使ってもっと便利に!

Makefile内では変数(マクロとも呼ばれます)を使うことで、繰り返し記述する文字列をまとめたり、設定変更を容易にしたりできます。変数は通常、大文字で定義されます。

変数定義の基本的な構文は以下の通りです。

変数名 = 値

または

変数名 := 値

=:= の違いは少し複雑ですが、初めのうちは := (単純展開変数)を使うのが安全でおすすめです。これは、変数が定義された時点で値が確定する方式です。

変数を使うときは $(変数名) または ${変数名} のように書きます。

よく使われる変数

Makefileでは、慣例的によく使われる変数名があります。

変数名説明
CCCコンパイラのコマンド名gcc, clang
CFLAGSCコンパイラに渡すオプション(警告、最適化など)-Wall -g -O2
LDFLAGSリンカに渡すオプション(ライブラリのパスなど)-L/usr/local/lib
LDLIBSリンクするライブラリの指定-lm -lpthread
TARGET生成する実行ファイル名my_program
OBJSオブジェクトファイル(.o)のリストmain.o module1.o module2.o
SRCSソースファイル(.c)のリストmain.c module1.c module2.c

変数を使ったMakefileの例

# 変数定義
CC := gcc
CFLAGS := -Wall -g
TARGET := hello
SRCS := hello.c
OBJS := $(SRCS:.c=.o) # .cを.oに置換する便利な書き方

# ルール
$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# ターゲット`clean`を追加 (後述)
.PHONY: clean
clean:
	rm -f $(TARGET) $(OBJS)

この例では、コンパイラの種類やオプション、ターゲット名を変更したい場合に、冒頭の変数定義部分だけを修正すれば良くなり、保守性が向上します。✨

また、%.o: %.c というルールはパターンルールと呼ばれ、「すべての `.c` ファイルから対応する `.o` ファイルを生成するルール」を簡潔に記述できます。ここで使われている $< は最初の依存ファイル名(ここでは %.c にマッチしたファイル名)、$@ はターゲット名(ここでは %.o にマッチしたファイル名)を表す自動変数です。

複数のソースファイルを扱う

プロジェクトが大きくなると、ソースファイルを機能ごとに分割しますね。例えば main.c, calc.c, display.c のような構成です。

このような場合、Makefileは以下のように書けます。

CC := gcc
CFLAGS := -Wall -g
TARGET := my_app
SRCS := main.c calc.c display.c
OBJS := $(SRCS:.c=.o) # main.o calc.o display.o に展開される

# デフォルトターゲット (makeコマンドで最初に実行される)
all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)

# パターンルールでオブジェクトファイルの生成を記述
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# クリーンアップ用のターゲット
.PHONY: clean
clean:
	rm -f $(TARGET) $(OBJS)

オブジェクトファイル (.o) の重要性

なぜ直接ソースファイルから実行ファイルを生成せず、一度オブジェクトファイル(.o)を経由するのでしょうか?

それは、効率的な再コンパイルのためです。もし calc.c だけを修正した場合、makecalc.o だけを再コンパイルし、その後、既存の main.o, display.o と新しくなった calc.o をリンクして my_app を再生成します。main.cdisplay.c は変更されていないので、再コンパイルの必要はありません。これにより、大規模なプロジェクトでもビルド時間を大幅に短縮できます。⏳➡️🚀

`clean` ターゲットと `.PHONY`

Makefileには、生成されたファイル(実行ファイルやオブジェクトファイル)を削除するためのルールを用意するのが一般的です。これを clean ターゲットと呼びます。

clean:
	rm -f $(TARGET) $(OBJS)

これを実行するには、make clean とします。

ところで、.PHONY: clean という記述は何でしょうか? これは、clean というターゲットが、実際のファイル名ではなく、単なるコマンド実行のためのラベル(偽りのターゲット)であることを make に伝えるためのものです。

もし .PHONY がなく、偶然 clean という名前のファイルがディレクトリに存在した場合、make clean を実行しても、「clean は最新です」と表示され、rm コマンドが実行されない可能性があります。.PHONY で指定することで、同名のファイルが存在しても常にコマンドが実行されるようになります。

一般的に、allcleaninstall のような、ファイル生成を伴わないアクションを表すターゲットは .PHONY として宣言します。

まとめ

今回は、C言語プログラミングにおけるMakefileの基本的な作成方法と使い方について解説しました。

  • Makefileは ターゲット: 依存関係コマンド で構成されるルールの集まり。
  • 変数を活用することで、Makefileの可読性と保守性を高められる。
  • オブジェクトファイル(.o)を中間生成物とすることで、効率的な再コンパイルが可能になる。
  • .PHONY は、ファイル名と衝突しないアクション用ターゲットを宣言するために使う。

Makefileを使いこなせるようになると、コーディング後のビルド作業が格段に楽になります🎉。最初は少し難しく感じるかもしれませんが、簡単なプロジェクトからMakefileを作成する習慣をつけることをお勧めします。

さらに高度なMakefileの機能(条件分岐、関数の利用、再帰的makeなど)や、CMakeのようなより高機能なビルドシステム生成ツールもありますが、まずは基本をしっかり押さえることが重要です。💪

参考情報

これらのリソースも参考に、Makefileへの理解を深めてみてくださいね!

コメント

タイトルとURLをコピーしました