1つの .c ファイルから「ヘッダ・ソース分離+Makefile」の実践構成に移行する。
// 宣言(プロトタイプ)のみを置く #ifndef MATHUTIL_H #define MATHUTIL_H int add(int a, int b); int mul(int a, int b); int factorial(int n); #endif
#include "mathutil.h" int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } int factorial(int n) { if (n <= 1) return 1; return n * factorial(n - 1); }
#include <stdio.h> #include "mathutil.h" int main(void) { printf("2+3 = %d\n", add(2, 3)); printf("4*5 = %d\n", mul(4, 5)); printf("5! = %d\n", factorial(5)); return 0; }
.h は「このモジュールが外に提供するもの(宣言)」の一覧。.c は「実際にどう動くか(実装)」。ヘッダだけ見れば関数の使い方が分かるのが理想。main.c だけ変更したら gcc -c main.c と最後の gcc main.o mathutil.o -o app の2コマンドで済む(mathutil は再コンパイル不要)。これを自動化するのが Makefile。| オプション | 意味 |
|---|---|
-c | コンパイルのみ(リンクしない)。.o ファイル生成 |
-o name | 出力ファイル名を指定 |
-Wall -Wextra | 警告を広めに出す。常に付けるべき |
-g | デバッグ情報付き(gdb で使う) |
-O0 / -O2 | 最適化なし / 標準的最適化 |
-std=c11 / -std=c17 | C言語規格のバージョンを指定 |
-I dir | ヘッダ検索パスを追加 |
-L dir | ライブラリ検索パスを追加 |
-l name | ライブラリ libname.a/so をリンク(例: -lm で math) |
-D MACRO | プリプロセッサにマクロを定義(#define MACRO 相当) |
// mathutil.h の冒頭と末尾 #ifndef MATHUTIL_H #define MATHUTIL_H /* ヘッダの中身 */ #endif
#pragma once 1行でも同じ効果。ただし標準ではないので、教科書では #ifndef を勧めることが多い。#ifndef CONFIG_H #define CONFIG_H extern int g_verbose; // 宣言のみ(実体はどこかに) #endif
#include "config.h" int g_verbose = 0; // 実体の定義(唯一1箇所)
int g_verbose; と書くのはNG: include した全ての .c で実体が定義されてしまい、リンカエラー(multiple definition)になる。Makefile を読んで「どのファイルが変更されたか」を判定し、必要な部分だけビルドします。最小の Makefile から始めましょう。# Makefile(ファイル名は必ず大文字M) # 注意: インデントは必ずタブ文字 app: main.o mathutil.o gcc main.o mathutil.o -o app main.o: main.c mathutil.h gcc -c main.c mathutil.o: mathutil.c mathutil.h gcc -c mathutil.c clean: rm -f *.o app
CC = gcc CFLAGS = -Wall -Wextra -O2 -g OBJS = main.o mathutil.o TARGET = app $(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET) # .c → .o の変換は暗黙ルールが適用される # make は自動的に $(CC) $(CFLAGS) -c foo.c -o foo.o を実行 main.o: main.c mathutil.h mathutil.o: mathutil.c mathutil.h clean: rm -f $(OBJS) $(TARGET) .PHONY: clean
clean というファイルが実在しないのに常に実行したい場合に宣言する。ファイルがあると「up to date」になってしまうのを防ぐ。# gcc の -MMD で .d ファイル(依存関係)を生成
CFLAGS += -MMD
-include $(OBJS:.o=.d)
make → ./app で実行できるところまで。sqrt()(<math.h>)を使う関数を追加し、Makefile の CFLAGS または LDFLAGS に -lm を追加してビルド成功させよ。make debug で -g -O0、make release で -O2 になるように Makefile に2つのターゲットを追加せよ。#include "x.h" のときに出るエラーメッセージを確認せよ。その後 guard を戻して再ビルド。