プリプロセッサ
プリプロセッサは建設前の設計図確認のようなものです。実際のコンパイルが始まる前に、マクロを展開し、ヘッダファイルを挿入し、条件分岐を刈り込み、その結果をコンパイラに渡します。
プリプロセスの流れ
C言語のコンパイル過程:ソースファイル → プリプロセス → コンパイル → アセンブル → リンク。プリプロセッサはコンパイルの前に実行され、# で始まるすべての指示を処理します。
-E オプションを使うとプリプロセス結果だけを確認できます:
gcc -E hello.c -o hello.i
#include ヘッダファイル
#include は指定したファイルの内容をそのまま現在の位置に挿入します。
2つの形式
#include <stdio.h>
#include "myheader.h"
< >はシステムディレクトリを検索します" "はカレントディレクトリを先に検索し、次にシステムディレクトリを検索します
" " を、標準ライブラリのヘッダには < > を使うのが慣例です。
代表的な標準ヘッダ
| ヘッダ | 主な内容 |
|---|---|
stdio.h |
入出力関数 |
stdlib.h |
メモリ割り当て、型変換、乱数 |
string.h |
文字列操作 |
math.h |
数学関数 |
ctype.h |
文字分類 |
assert.h |
アサートマクロ |
#define マクロ定義
オブジェクト形式マクロ
名前付き定数を定義します:
#define PI 3.14159265
#define MAX_SIZE 100
#define AUTHOR "WebTutorial"
double area = PI * 10 * 10;
int arr[MAX_SIZE];
printf("Author: %s\n", AUTHOR);
関数形式マクロ
パラメータ付きマクロは関数のように見えますが、テキスト置換です:
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int s = SQUARE(5);
int m = MAX(10, 20);
25
20
マクロの落とし穴と括弧
落とし穴1:括弧の欠落による優先順位エラー
#define DOUBLE(x) x + x
int result = DOUBLE(3) * 2;
展開後は 3 + 3 * 2 となり、期待の12ではなく9になります。正しいバージョン:
#define DOUBLE(x) ((x) + (x))
落とし穴2:副作用のあるパラメータ
#define SQUARE(x) ((x) * (x))
int n = 3;
int s = SQUARE(n++);
展開後は ((n++) * (n++)) となり、n が2回インクリメントされます。これは未定義動作です。
i++、func() など)をマクロ引数に渡してはいけません。インライン関数や通常の関数を使用してください。
落とし穴3:if-else分岐内のマクロ
#define CHECK(cond) if (cond) printf("yes\n")
if (flag)
CHECK(x);
else
do_something();
展開後、else はマクロ内側の if と対応し、外側の if とは対応しません。これが古典的なダングリングelse問題です。do { ... } while(0) で囲んでください:
#define CHECK(cond) do { if (cond) printf("yes\n"); } while(0)
例
#include <stdio.h>
#define SAFE_MAX(a, b) ((a) > (b) ? (a) : (b))
#define SAFE_SQUARE(x) ((x) * (x))
#define SWAP(type, a, b) do { type _tmp = (a); (a) = (b); (b) = _tmp; } while(0)
int main(void) {
int x = 10, y = 20;
printf("max: %d\n", SAFE_MAX(x, y));
printf("square: %d\n", SAFE_SQUARE(5));
SWAP(int, x, y);
printf("after swap: x=%d y=%d\n", x, y);
double a = 3.5, b = 2.1;
printf("double max: %.1f\n", SAFE_MAX(a, b));
return 0;
}
max: 20
square: 25
after swap: x=20 y=10
double max: 3.5
条件コンパイル
条件コンパイルにより、同じソースコードから条件に応じて異なるコンパイル結果を生成できます。クロスプラットフォーム対応、デバッグスイッチ、機能の刈り込みなどで広く使われます。
#if / #elif / #else / #endif
#define PLATFORM 2
#if PLATFORM == 1
const char *os = "Windows";
#elif PLATFORM == 2
const char *os = "Linux";
#else
const char *os = "Unknown";
#endif
printf("Platform: %s\n", os);
Platform: Linux
#ifdef / #ifndef
#ifdef はマクロが定義されているかを確認し、#ifndef は定義されていないかを確認します。
#define DEBUG
#ifdef DEBUG
printf("Debug mode: x=%d\n", x);
#endif
#ifndef BUFFER_SIZE
#define BUFFER_SIZE 256
#endif
#ifdef DEBUG は #if defined(DEBUG) と等価です。#ifndef は #if !defined(...) と等価です。
デバッグスイッチ
#ifdef DEBUG
#define LOG(msg) printf("[DEBUG] %s:%d %s\n", __FILE__, __LINE__, msg)
#else
#define LOG(msg)
#endif
LOG("Variable initialized");
-DDEBUG 付きでコンパイルするとログ出力が有効になり、付けなければプログラムは何も出力しません:
gcc -DDEBUG -o myapp myapp.c
例
#include <stdio.h>
#define LEVEL 3
int main(void) {
#if LEVEL >= 3
printf("Advanced features enabled\n");
#elif LEVEL >= 2
printf("Intermediate features enabled\n");
#else
printf("Basic features\n");
#endif
#ifdef VERBOSE
printf("Verbose output mode\n");
#else
printf("Concise output mode\n");
#endif
return 0;
}
Advanced features enabled
Concise output mode
定義済みマクロ
C標準ではいくつかの便利なマクロが定義済みです:
| マクロ | 意味 |
|---|---|
__FILE__ |
現在のソースファイル名 |
__LINE__ |
現在の行番号 |
__DATE__ |
コンパイル日付 |
__TIME__ |
コンパイル時刻 |
__func__ |
現在の関数名(C99) |
__STDC__ |
ANSI C標準に準拠しているか |
printf("File: %s\n", __FILE__);
printf("Line: %d\n", __LINE__);
printf("Date: %s\n", __DATE__);
printf("Time: %s\n", __TIME__);
printf("Function: %s\n", __func__);
File: main.c
Line: 5
Date: Jun 25 2026
Time: 14:30:00
Function: main
#undef でマクロを取り消す
#undef は以前のマクロ定義を取り消します:
#define MAX_SIZE 100
printf("%d\n", MAX_SIZE);
#undef MAX_SIZE
#undef の後、MAX_SIZE はもはやマクロではなく、再定義や通常の識別子として使用できます。
❓ よくある質問
#if defined の方が柔軟です。複数の条件を組み合わせられます:#if defined(A) && defined(B)。#ifdef は1つのマクロしか確認できません。📖 まとめ
#includeはヘッダファイルの内容をソースに挿入します。< >はシステムディレクトリ、" "はカレントディレクトリを検索します#defineはオブジェクト形式マクロ(定数置換)と関数形式マクロ(パラメータ付きテキスト置換)を定義します- マクロのパラメータには括弧を付け、マクロ式全体も括弧で囲んで優先順位の罠を避けてください
- 副作用のある式をマクロ引数に渡してはいけません
#if/#ifdef/#ifndefによる条件コンパイルはクロスプラットフォーム対応、デバッグスイッチ、機能刈り込みに有効です__FILE__、__LINE__、__DATE__などの定義済みマクロはログ出力やデバッグに便利です
📝 練習問題
- 括弧ありと括弧なしの2つのバージョンのSQUAREマクロを定義し、
SQUARE(2+3)で両方をテストして結果の違いを観察してください #ifdef DEBUGを使ってログマクロLOG(fmt, ...)を実装してください。DEBUGモードではファイル名と行番号を出力し、そうでなければ何も出力しません- 条件コンパイルを使ってクロスプラットフォーム出力を実装してください。
#if defined(_WIN32)でプラットフォームを検出し、Windowsなら「Windows platform」、Linuxなら「Linux platform」と出力します



