プリプロセッサ

プリプロセッサは建設前の設計図確認のようなものです。実際のコンパイルが始まる前に、マクロを展開し、ヘッダファイルを挿入し、条件分岐を刈り込み、その結果をコンパイラに渡します。

プリプロセスの流れ

C言語のコンパイル過程:ソースファイル → プリプロセス → コンパイル → アセンブル → リンク。プリプロセッサはコンパイルの前に実行され、# で始まるすべての指示を処理します。

-E オプションを使うとプリプロセス結果だけを確認できます:

BASH
gcc -E hello.c -o hello.i

#include ヘッダファイル

#include は指定したファイルの内容をそのまま現在の位置に挿入します。

2つの形式

C
#include <stdio.h>
#include "myheader.h"
💡 ヒント: 自作のヘッダには " " を、標準ライブラリのヘッダには < > を使うのが慣例です。

代表的な標準ヘッダ

ヘッダ 主な内容
stdio.h 入出力関数
stdlib.h メモリ割り当て、型変換、乱数
string.h 文字列操作
math.h 数学関数
ctype.h 文字分類
assert.h アサートマクロ

#define マクロ定義

オブジェクト形式マクロ

名前付き定数を定義します:

C
#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);
⚠️ 注意: マクロ定義の末尾にセミコロンを付けてはいけません。付けると、そのセミコロンも置換の一部となりエラーの原因になります。

関数形式マクロ

パラメータ付きマクロは関数のように見えますが、テキスト置換です:

C
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int s = SQUARE(5);
int m = MAX(10, 20);
TEXT
25
20
💡 ヒント: プリプロセッサは単純なテキスト置換を行います。型チェックも式評価もありません。

マクロの落とし穴と括弧

落とし穴1:括弧の欠落による優先順位エラー

C
#define DOUBLE(x) x + x
C
int result = DOUBLE(3) * 2;

展開後は 3 + 3 * 2 となり、期待の12ではなく9になります。正しいバージョン:

C
#define DOUBLE(x) ((x) + (x))

落とし穴2:副作用のあるパラメータ

C
#define SQUARE(x) ((x) * (x))
C
int n = 3;
int s = SQUARE(n++);

展開後は ((n++) * (n++)) となり、n が2回インクリメントされます。これは未定義動作です。

⚠️ 注意: 副作用のある式(i++func() など)をマクロ引数に渡してはいけません。インライン関数や通常の関数を使用してください。

落とし穴3:if-else分岐内のマクロ

C
#define CHECK(cond) if (cond) printf("yes\n")
C
if (flag)
    CHECK(x);
else
    do_something();

展開後、else はマクロ内側の if と対応し、外側の if とは対応しません。これが古典的なダングリングelse問題です。do { ... } while(0) で囲んでください:

C
#define CHECK(cond) do { if (cond) printf("yes\n"); } while(0)

C
#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;
}
▶ 試してみよう
TEXT
max: 20
square: 25
after swap: x=20 y=10
double max: 3.5

条件コンパイル

条件コンパイルにより、同じソースコードから条件に応じて異なるコンパイル結果を生成できます。クロスプラットフォーム対応、デバッグスイッチ、機能の刈り込みなどで広く使われます。

#if / #elif / #else / #endif

C
#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);
TEXT
Platform: Linux

#ifdef / #ifndef

#ifdef はマクロが定義されているかを確認し、#ifndef は定義されていないかを確認します。

C
#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(...) と等価です。

デバッグスイッチ

C
#ifdef DEBUG
#define LOG(msg) printf("[DEBUG] %s:%d %s\n", __FILE__, __LINE__, msg)
#else
#define LOG(msg)
#endif

LOG("Variable initialized");

-DDEBUG 付きでコンパイルするとログ出力が有効になり、付けなければプログラムは何も出力しません:

BASH
gcc -DDEBUG -o myapp myapp.c

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;
}
▶ 試してみよう
TEXT
Advanced features enabled
Concise output mode

定義済みマクロ

C標準ではいくつかの便利なマクロが定義済みです:

マクロ 意味
__FILE__ 現在のソースファイル名
__LINE__ 現在の行番号
__DATE__ コンパイル日付
__TIME__ コンパイル時刻
__func__ 現在の関数名(C99)
__STDC__ ANSI C標準に準拠しているか
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__);
TEXT
File: main.c
Line: 5
Date: Jun 25 2026
Time: 14:30:00
Function: main
💡 ヒント: カスタムログマクロは定義済みマクロを組み合わせて、ファイル名や行番号などの診断情報を出力することがよくあります。

#undef でマクロを取り消す

#undef は以前のマクロ定義を取り消します:

C
#define MAX_SIZE 100
printf("%d\n", MAX_SIZE);
#undef MAX_SIZE

#undef の後、MAX_SIZE はもはやマクロではなく、再定義や通常の識別子として使用できます。

❓ よくある質問

Q なぜマクロ定義の末尾にセミコロンを付けてはいけないのですか?
A マクロはテキスト置換です。セミコロンを付けるとそれも置換後のテキストの一部となり、if-elseなどで構文エラーの原因になります。
Q 関数形式マクロとインライン関数はどちらを使うべきですか?
A インライン関数には型チェックがあり、引数は1回しか評価されず、デバッグも容易です。関数形式マクロは柔軟ですが安全ではありません。インライン関数を優先し、SWAPのような型汎用操作などの特別な場合のみマクロを使ってください。
Q #ifdefと#if definedに違いはありますか?
A 機能的には同じですが、#if defined の方が柔軟です。複数の条件を組み合わせられます:#if defined(A) && defined(B)#ifdef は1つのマクロしか確認できません。
Q 条件コンパイルとif文の違いは何ですか?
A 条件コンパイルはプリプロセス段階でコードを刈り込みます。選択されなかった分岐はコンパイルされません。if文ではすべての分岐がコンパイルされ、実行時のパスが異なるだけです。条件コンパイルはコンパイルできないコード(プラットフォーム固有のヘッダなど)を除外できます。

📖 まとめ

📝 練習問題

  1. 括弧ありと括弧なしの2つのバージョンのSQUAREマクロを定義し、SQUARE(2+3) で両方をテストして結果の違いを観察してください
  2. #ifdef DEBUG を使ってログマクロ LOG(fmt, ...) を実装してください。DEBUGモードではファイル名と行番号を出力し、そうでなければ何も出力しません
  3. 条件コンパイルを使ってクロスプラットフォーム出力を実装してください。#if defined(_WIN32) でプラットフォームを検出し、Windowsなら「Windows platform」、Linuxなら「Linux platform」と出力します
100%