第9章: プリプロセッサ

9.1 プリプロセッサの理論

プリプロセッサとは

プリプロセッサ(Preprocessor)は、コンパイル前にソースコードを変換するツールである。

歴史的背景:

  • 1972年、Alan Snyderによって最初のCプリプロセッサが実装
  • 当初はアセンブラのマクロ機能を参考に設計
  • 後にCの言語仕様の一部として標準化
  • ソースコード処理の流れ:
    
    ソースファイル (.c)
           ↓
      プリプロセッサ    ← #include, #define, #ifdef などを処理
           ↓
      翻訳単位 (.i)     ← 純粋なC言語コード
           ↓
       コンパイラ
           ↓
      オブジェクトファイル (.o)
    

    プリプロセッサの役割

    プリプロセッサはテキスト変換を行う:

  • ファイルインクルード: #include
  • マクロ展開: #define
  • 条件付きコンパイル: #if, #ifdef
  • 行制御: #line
  • エラー生成: #error, #warning
  • プラグマ: #pragma
  • 9.2 ファイルインクルード

    #include ディレクティブ

    /* システムヘッダ: 標準の検索パスを使用 */
    #include <stdio.h>
    #include <stdlib.h>
    
    /* ユーザーヘッダ: カレントディレクトリから検索 */
    #include "myheader.h"
    #include "path/to/header.h"
    

    検索パスの違い

    #include <header.h>:
    1. コンパイラの標準インクルードパス
       /usr/include
       /usr/local/include
       など
    
    #include "header.h":
    1. カレントディレクトリ
    2. -I オプションで指定されたパス
    3. 標準インクルードパス(フォールバック)
    

    インクルードガード

    /* header.h */
    #ifndef HEADER_H
    #define HEADER_H
    
    /* ヘッダの内容 */
    struct Point {
        int x, y;
    };
    
    void process_point(struct Point *p);
    
    #endif /* HEADER_H */
    

    #pragma once(非標準だが広く使用)

    /* header.h */
    #pragma once
    
    /* ヘッダの内容 */
    /* インクルードガードと同じ効果 */
    

    インクルードガードの重要性

    /* 問題: 多重インクルード */
    /* main.c */
    #include "a.h"  /* a.h が b.h をインクルード */
    #include "b.h"  /* b.h が再度読み込まれる */
    
    /* b.h にインクルードガードがないと:
       - 同じ定義が2回現れる
       - コンパイルエラーまたは未定義動作 */
    

    9.3 マクロ定義

    オブジェクト形式マクロ

    /* 定数の定義 */
    #define PI 3.14159265358979
    #define MAX_BUFFER_SIZE 1024
    #define VERSION "1.0.0"
    
    /* 複数行マクロ(バックスラッシュで継続) */
    #define LONG_STRING "This is a very long string that \
    spans multiple lines in the source code"
    
    /* 空のマクロ */
    #define DEBUG  /* 値なし、存在のみをチェック */
    

    関数形式マクロ

    /* 基本的な関数形式マクロ */
    #define SQUARE(x) ((x) * (x))
    #define MAX(a, b) ((a) > (b) ? (a) : (b))
    #define MIN(a, b) ((a) < (b) ? (a) : (b))
    
    /* 使用 */
    int result = SQUARE(5);      /* ((5) * (5)) → 25 */
    int m = MAX(10, 20);         /* ((10) > (20) ? (10) : (20)) → 20 */
    

    マクロの括弧の重要性

    /* 悪い例: 括弧なし */
    #define SQUARE_BAD(x) x * x
    
    int a = SQUARE_BAD(3 + 2);  /* 3 + 2 * 3 + 2 = 11(期待は25) */
    
    /* 良い例: 括弧あり */
    #define SQUARE_GOOD(x) ((x) * (x))
    
    int b = SQUARE_GOOD(3 + 2); /* ((3 + 2) * (3 + 2)) = 25 */
    

    二重評価問題

    #define MAX(a, b) ((a) > (b) ? (a) : (b))
    
    int x = 5;
    int y = MAX(x++, 3);  /* x++ が2回評価される可能性 */
    /* 展開: ((x++) > (3) ? (x++) : (3)) */
    /* x の値が予測困難 */
    
    /* 解決策1: GCC拡張 */
    #define MAX_SAFE(a, b) ({      \
        __typeof__(a) _a = (a);    \
        __typeof__(b) _b = (b);    \
        _a > _b ? _a : _b;         \
    })
    
    /* 解決策2: インライン関数を使う */
    static inline int max_int(int a, int b)
    {
        return a > b ? a : b;
    }
    

    特殊な演算子

    /* # 演算子: 文字列化 */
    #define STRINGIFY(x) #x
    #define TOSTRING(x) STRINGIFY(x)
    
    printf("%s\n", STRINGIFY(hello));  /* "hello" */
    
    #define VERSION_MAJOR 1
    printf("%s\n", TOSTRING(VERSION_MAJOR));  /* "1" */
    
    /* ## 演算子: トークン連結 */
    #define CONCAT(a, b) a ## b
    #define MAKE_VAR(n) var_ ## n
    
    int MAKE_VAR(1) = 10;  /* int var_1 = 10; */
    int CONCAT(my, var) = 20;  /* int myvar = 20; */
    

    可変引数マクロ(C99)

    /* __VA_ARGS__: 可変引数を展開 */
    #define DEBUG_PRINT(fmt, ...) \
        printf("[DEBUG] " fmt "\n", __VA_ARGS__)
    
    DEBUG_PRINT("x = %d, y = %d", 10, 20);
    /* → printf("[DEBUG] " "x = %d, y = %d" "\n", 10, 20); */
    
    /* ##__VA_ARGS__: 引数がない場合のカンマを削除(GCC拡張) */
    #define LOG(fmt, ...) \
        printf(fmt, ##__VA_ARGS__)
    
    LOG("Hello");  /* printf("Hello"); カンマが削除される */
    
    /* C23: __VA_OPT__ */
    #define LOG_C23(fmt, ...) \
        printf(fmt __VA_OPT__(,) __VA_ARGS__)
    

    事前定義マクロ

    /* 標準の事前定義マクロ */
    __FILE__      /* 現在のファイル名 */
    __LINE__      /* 現在の行番号 */
    __DATE__      /* コンパイル日 "Mmm dd yyyy" */
    __TIME__      /* コンパイル時刻 "hh:mm:ss" */
    __func__      /* 現在の関数名(C99) */
    __STDC__      /* 標準Cコンパイラなら1 */
    __STDC_VERSION__  /* C標準のバージョン */
    
    /* 使用例: デバッグ出力 */
    #define DEBUG_LOG(msg) \
        fprintf(stderr, "%s:%d (%s): %s\n", \
                __FILE__, __LINE__, __func__, msg)
    
    void my_function(void)
    {
        DEBUG_LOG("Entering function");
        /* 出力: main.c:42 (my_function): Entering function */
    }
    

    マクロの定義解除

    #define TEMP 100
    /* TEMP を使用 */
    #undef TEMP
    /* TEMP は未定義 */
    
    /* 再定義(警告なしに)*/
    #define TEMP 200
    

    9.4 条件付きコンパイル

    基本構文

    #if expression
        /* expression が真のときコンパイル */
    #elif expression2
        /* expression2 が真のときコンパイル */
    #else
        /* どれも真でないときコンパイル */
    #endif
    

    #ifdef と #ifndef

    /* マクロが定義されているか */
    #ifdef DEBUG
        printf("Debug mode\n");
    #endif
    
    /* マクロが定義されていないか */
    #ifndef RELEASE
        printf("Not release mode\n");
    #endif
    
    /* 同等の書き方 */
    #if defined(DEBUG)
        /* ... */
    #endif
    
    #if !defined(RELEASE)
        /* ... */
    #endif
    

    複雑な条件

    /* 論理演算子 */
    #if defined(LINUX) && defined(X86_64)
        /* Linux 64bit */
    #elif defined(LINUX) && defined(ARM)
        /* Linux ARM */
    #elif defined(WINDOWS)
        /* Windows */
    #else
        #error "Unsupported platform"
    #endif
    
    /* 値の比較 */
    #if __STDC_VERSION__ >= 201112L
        /* C11以降 */
        #define HAVE_STATIC_ASSERT 1
    #elif __STDC_VERSION__ >= 199901L
        /* C99 */
        #define HAVE_STATIC_ASSERT 0
    #endif
    

    プラットフォーム判定

    /* コンパイラ判定 */
    #if defined(__GNUC__)
        #define COMPILER "GCC"
    #elif defined(__clang__)
        #define COMPILER "Clang"
    #elif defined(_MSC_VER)
        #define COMPILER "MSVC"
    #else
        #define COMPILER "Unknown"
    #endif
    
    /* OS判定 */
    #if defined(__linux__)
        #define OS "Linux"
    #elif defined(__APPLE__)
        #define OS "macOS"
    #elif defined(_WIN32)
        #define OS "Windows"
    #else
        #define OS "Unknown"
    #endif
    
    /* アーキテクチャ判定 */
    #if defined(__x86_64__) || defined(_M_X64)
        #define ARCH "x86_64"
    #elif defined(__i386__) || defined(_M_IX86)
        #define ARCH "x86"
    #elif defined(__aarch64__)
        #define ARCH "ARM64"
    #elif defined(__arm__)
        #define ARCH "ARM"
    #else
        #define ARCH "Unknown"
    #endif
    

    デバッグ機能の制御

    #ifdef NDEBUG
        /* リリースモード */
        #define ASSERT(expr) ((void)0)
        #define DEBUG_ONLY(code)
    #else
        /* デバッグモード */
        #define ASSERT(expr) \
            ((expr) ? (void)0 : \
             (fprintf(stderr, "Assertion failed: %s at %s:%d\n", \
                      #expr, __FILE__, __LINE__), abort()))
        #define DEBUG_ONLY(code) code
    #endif
    
    /* 使用 */
    ASSERT(ptr != NULL);
    DEBUG_ONLY(printf("Debug: ptr = %p\n", ptr);)
    

    9.5 その他のディレクティブ

    #error と #warning

    /* コンパイルを停止 */
    #if !defined(VERSION)
        #error "VERSION must be defined"
    #endif
    
    /* 警告を出力(GCC/Clang拡張) */
    #if BUFFER_SIZE < 256
        #warning "BUFFER_SIZE is very small"
    #endif
    

    #line

    /* 行番号とファイル名を変更 */
    #line 100 "generated_code.c"
    /* 以降のコードは generated_code.c の100行目として報告される */
    
    /* 主な用途: コードジェネレータ */
    

    #pragma

    /* コンパイラ固有の指示 */
    
    /* GCC: 警告の制御 */
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wunused-variable"
    int unused_var;
    #pragma GCC diagnostic pop
    
    /* 構造体のパッキング */
    #pragma pack(push, 1)
    struct Packed {
        char a;
        int b;
    };
    #pragma pack(pop)
    
    /* 標準化されたプラグマ(C99) */
    #pragma STDC FP_CONTRACT ON
    #pragma STDC FENV_ACCESS OFF
    #pragma STDC CX_LIMITED_RANGE ON
    

    _Pragma 演算子(C99)

    /* マクロ内でプラグマを使用 */
    #define DO_PRAGMA(x) _Pragma(#x)
    #define DISABLE_WARNING(name) \
        DO_PRAGMA(GCC diagnostic ignored #name)
    
    DISABLE_WARNING(-Wunused-variable)
    /* 展開: _Pragma("GCC diagnostic ignored \"-Wunused-variable\"") */
    

    9.6 マクロの設計パターン

    ラッパーマクロ

    /* システムコールのラッパー */
    #define SAFE_CLOSE(fd) do { \
        if ((fd) >= 0) {        \
            close(fd);          \
            (fd) = -1;          \
        }                       \
    } while (0)
    
    /* メモリ管理のラッパー */
    #define SAFE_FREE(ptr) do { \
        free(ptr);              \
        (ptr) = NULL;           \
    } while (0)
    

    do-while(0) イディオム

    /* なぜ do-while(0) を使うのか */
    
    /* 悪い例: 中括弧のみ */
    #define BAD_MACRO(x) { stmt1; stmt2; }
    
    if (condition)
        BAD_MACRO(x);  /* セミコロンの後に空文が入る */
    else
        other();       /* 構文エラーの可能性 */
    
    /* 良い例: do-while(0) */
    #define GOOD_MACRO(x) do { stmt1; stmt2; } while (0)
    
    if (condition)
        GOOD_MACRO(x);  /* セミコロンが自然に収まる */
    else
        other();        /* 正常にコンパイル */
    

    コンパイル時アサート

    /* C11以前 */
    #define STATIC_ASSERT(expr, msg) \
        typedef char static_assertion_##msg[(expr) ? 1 : -1]
    
    STATIC_ASSERT(sizeof(int) == 4, int_must_be_4_bytes);
    
    /* C11以降 */
    #include <assert.h>
    static_assert(sizeof(int) == 4, "int must be 4 bytes");
    

    X-Macro

    /* X-Macroパターン: コードの重複を避ける */
    
    /* 定義ファイル */
    #define COLOR_LIST \
        X(RED,   0xFF0000) \
        X(GREEN, 0x00FF00) \
        X(BLUE,  0x0000FF)
    
    /* enum定義 */
    enum Color {
        #define X(name, value) COLOR_##name,
        COLOR_LIST
        #undef X
        COLOR_COUNT
    };
    
    /* 文字列配列 */
    const char *color_names[] = {
        #define X(name, value) #name,
        COLOR_LIST
        #undef X
    };
    
    /* 値配列 */
    int color_values[] = {
        #define X(name, value) value,
        COLOR_LIST
        #undef X
    };
    

    汎用マクロ(C11 _Generic)

    /* 型に応じた関数選択 */
    #define print_value(x) _Generic((x), \
        int: print_int, \
        double: print_double, \
        char *: print_string, \
        default: print_unknown \
    )(x)
    
    void print_int(int x) { printf("%d\n", x); }
    void print_double(double x) { printf("%f\n", x); }
    void print_string(char *x) { printf("%s\n", x); }
    void print_unknown(void *x) { printf("unknown\n"); }
    
    /* 使用 */
    print_value(42);      /* print_int(42) */
    print_value(3.14);    /* print_double(3.14) */
    print_value("hello"); /* print_string("hello") */
    

    9.7 プリプロセッサの落とし穴

    名前空間の汚染

    /* 悪い例: 一般的な名前 */
    #define MAX 100
    #define min(a, b) ((a) < (b) ? (a) : (b))
    
    /* 良い例: プレフィックスを付ける */
    #define MYLIB_MAX 100
    #define MYLIB_MIN(a, b) ((a) < (b) ? (a) : (b))
    

    マクロと演算子の優先順位

    /* 悪い例 */
    #define ADD(a, b) a + b
    
    int x = ADD(1, 2) * 3;  /* 1 + 2 * 3 = 7(期待は9) */
    
    /* 良い例 */
    #define ADD(a, b) ((a) + (b))
    
    int y = ADD(1, 2) * 3;  /* ((1) + (2)) * 3 = 9 */
    

    マクロと副作用

    /* 引数に副作用があると問題 */
    #define ABS(x) ((x) < 0 ? -(x) : (x))
    
    int n = -5;
    int result = ABS(n++);  /* n が複数回評価される */
    
    /* 安全な代替 */
    static inline int abs_safe(int x)
    {
        return x < 0 ? -x : x;
    }
    

    文字列リテラルの連結

    /* プリプロセッサは隣接する文字列を連結する */
    printf("Hello, " "world!\n");  /* "Hello, world!\n" */
    
    /* マクロでの利用 */
    #define LOG_PREFIX "[LOG] "
    printf(LOG_PREFIX "Message: %s\n", msg);
    /* → printf("[LOG] " "Message: %s\n", msg); */
    /* → printf("[LOG] Message: %s\n", msg); */
    

    9.8 プリプロセッサ出力の確認

    # プリプロセス結果のみを出力
    gcc -E source.c -o source.i
    
    # マクロ展開を確認
    gcc -E -dM source.c  # 定義されている全マクロを表示
    
    # インクルードパスを確認
    gcc -v -E source.c
    

    9.9 まとめ

    本章では、Cプリプロセッサについて学んだ:

  • 基本: テキスト変換、処理の流れ
  • インクルード: ヘッダファイル、インクルードガード
  • マクロ: オブジェクト形式、関数形式、演算子
  • 条件付きコンパイル: #if, #ifdef, プラットフォーム判定
  • その他: #error, #pragma, _Pragma
  • 設計パターン: do-while(0), X-Macro, _Generic
  • 落とし穴: 名前空間、演算子優先順位、副作用
  • 次章では、入出力について学ぶ。

    ---

    参考文献

  • ISO/IEC 9899:2011, Programming languages — C
  • Kernighan, B. W., & Ritchie, D. M. (1988). "The C Programming Language", 2nd Edition, Prentice Hall
  • GCC Manual, "The C Preprocessor", https://gcc.gnu.org/onlinedocs/cpp/
  • Plauger, P. J. (1992). "The Standard C Library", Prentice Hall