閉じる

【C言語】#define入門 定数と簡単なマクロの書き方と落とし穴

C言語の#defineは、コンパイル前に動くプリプロセッサが文字列をそのまま置き換える仕組みです。

コードの可読性や保守性を上げるために、定数や簡単な式を名前で表現できます。

本記事では「定数や簡単なマクロを定義する」ことに焦点を当て、書き方から注意点、落とし穴まで初心者向けに丁寧に解説します。

引数付きマクロや条件コンパイルは別記事で扱います。

#defineとは?

プリプロセッサとマクロの基本

C言語のプリプロセッサは、コンパイル前にソースを走査し、#で始まるディレクティブを処理します。

#defineはその代表で、「名前」→「置換テキスト」の対応を作ります。

コンパイラが見る前に、名前が置換テキストへと文字通り差し替えられます。

どんな時に使うか

  • 数値や文字列の繰り返しを避けて可読性を上げたい時。
  • 定義を1箇所で管理し、変更に強いコードにしたい時。
  • 配列サイズやビットフラグなど、コンパイル時に確定している値を扱う時。

重要なのは#defineは「型を持たない文字列置換」だという点です

この性質は強力ですが、後述の落とし穴にもつながります。

マクロ置換のイメージ

置換はあくまでテキスト処理です。

以下のように展開されます。

C言語
// 元のコード
#define LEN 3
int main(void) {
    int a[LEN + 1]; // LEN は 3 に置き換わる
    return sizeof(a); // => sizeof(int) * 4 になる
}
C言語
// プリプロセス後のイメージ
int main(void) {
    int a[3 + 1];
    return sizeof(a);
}

#defineとconstの違い

#defineはプリプロセス段階の置換、constは型を持つ変数です。

違いを整理します。

  • 評価時期: #defineはコンパイル前に展開、constはコンパイル時に型チェックされ、実体は変数(最適化で埋め込みの場合もある)。
  • : #defineは型なし、constは明確な型を持つ。
  • デバッグ: constはデバッガで参照しやすいが、#defineは展開されて消える。
  • 使える場所: 配列長やcaseラベルなど「整数定数式」が必要な場面では#defineenumが適任。constは使えない場面があります。

表にまとめます。

観点#defineconst
展開/評価プリプロセス段階の文字列置換コンパイル時に型チェック、変数として存在
なしあり
デバッグの容易さ低い高い
配列長/caseラベル使える使えない場合がある
アドレス取得(&)不可可能
オーバーヘッドなし(埋め込み)最適化次第(多くは埋め込み)

安全性重視ならconst、コンパイル時定数やビット定数重視なら#defineやenumという使い分けが基本です。

#defineで定数を作る

基本の書式

#define 名前 置換テキストという形で書きます。

末尾にセミコロンは付けません

複雑な式は括弧で包むと安全です。

C言語
// 基本形
#define MAX_RETRY 3
#define APP_NAME "ExampleApp"
#define BUFFER_BYTES (4 * 1024) // 計算式は括弧で包む

数値定数

整数値の定義では桁の見やすさと型に注意します。

10進(123)、16進(0x7B)、8進(0173)が使えます。

Cでは桁区切り(1_000)は使えません。

C言語
#include <stdio.h>

#define MAX_RETRY 3
#define DEFAULT_PORT 8080
#define MASK_HEX 0xFF

int main(void) {
    printf("MAX_RETRY=%d\n", MAX_RETRY);
    printf("DEFAULT_PORT=%d\n", DEFAULT_PORT);
    printf("MASK_HEX=%d(=0x%X)\n", MASK_HEX, MASK_HEX);
    return 0;
}
実行結果
MAX_RETRY=3
DEFAULT_PORT=8080
MASK_HEX=255(=0xFF)

浮動小数点定数

浮動小数点にはfLのサフィックスを使い、型を明確にします。

倍精度(double)がデフォルトなので、floatで扱いたい場合はfを付けます。

C言語
#include <stdio.h>

#define PI_D 3.141592653589793    // double
#define PI_F 3.1415927f           // float
#define RATE 0.08                 // double

int main(void) {
    float pf = PI_F;     // floatとして代入
    double pd = PI_D;    // doubleとして代入
    printf("PI_F(float)=%.7f\n", pf);
    printf("PI_D(double)=%.15f\n", pd);
    printf("RATE=%.2f\n", RATE);
    return 0;
}
実行結果
PI_F(float)=3.1415927
PI_D(double)=3.141592653589793
RATE=0.08

文字列と文字の定数

文字列は"..."、文字は'A'のように定義できます。

文字列は読み取り専用領域に置かれる傾向があるため、ポインタの扱いに注意します。

C言語
#include <stdio.h>

#define MSG_HELLO "Hello, C!"
#define CHAR_YES 'Y'

int main(void) {
    const char *s = MSG_HELLO; // 文字列リテラルへのポインタ
    char c = CHAR_YES;         // 単一文字
    printf("%s %c\n", s, c);
    return 0;
}
実行結果
Hello, C! Y

型サフィックスの付け方

整数や浮動小数点にはサフィックスで型を付けられます。

計算時の型昇格やオーバーフロー回避に有効です。

種別サフィックス例備考
unsignedU10U, 0xFFU非負の整数
longL1000Llong型
unsigned longUL1000UL
long longLL100000LL
unsigned long longULL1ULL << 40大きなビットマスクなど
floatf/F3.14f単精度
long doubleL3.14L拡張倍精度
ヒント

<stdint.h>にはINT64_C(…)UINT32_C(…)のマクロがあり、幅を意識した定数を書くのに便利です。

命名ルール

マクロは大文字スネークケース(例: DEFAULT_TIMEOUT_MS)が慣例です。

プロジェクト接頭辞を付けると衝突を避けられます(例: APP_MAX_USERS)。

標準ヘッダに定義済みの名前(EOFNULL)を再定義しないように心がけます。

書く場所

そのファイルだけで使うなら、ソース先頭付近にまとめます。

複数ファイルで共有するなら.hヘッダに置き、必要な.cから#includeします。

スコープは定義位置からファイル末尾までです。限定したい時は#undefで解除します。

ヘッダの二重インクルード対策(インクルードガード)は重要ですが、詳細は別記事で扱います。

#defineで作る簡単なマクロ

計算式マクロ

引数を取らない式のかたまりを名前にできます。

必ず括弧で全体を包む癖を付けると安全です。

C言語
#include <stdio.h>

#define BASE_WIDTH  4
#define BASE_HEIGHT 3
#define RECT_AREA_COEFF (BASE_WIDTH * BASE_HEIGHT) // 12
#define DEFAULT_BUFFER (4u * 1024u)                // 4096

int main(void) {
    int area = RECT_AREA_COEFF; // 12
    printf("RECT_AREA_COEFF=%d\n", area);
    printf("DEFAULT_BUFFER=%u bytes\n", DEFAULT_BUFFER);
    return 0;
}
実行結果
RECT_AREA_COEFF=12
DEFAULT_BUFFER=4096 bytes

単位マクロ

サイズや時間など、単位変換をマクロで表すと読みやすくなります。

C言語
#include <stdio.h>

#define KB (1024u)
#define MB (KB * 1024u)
#define MS_PER_SEC (1000u)
#define TIMEOUT_MS (5u * MS_PER_SEC) // 5秒 = 5000ms

int main(void) {
    printf("KB=%u, MB=%u\n", KB, MB);
    printf("TIMEOUT_MS=%u\n", TIMEOUT_MS);
    return 0;
}
実行結果
KB=1024, MB=1048576
TIMEOUT_MS=5000

大きな値はULLを使うと桁あふれを避けやすくなります。

#define GB (1024ULL * 1024ULL * 1024ULL)

ビットマスクとフラグ

ビット演算は#defineの得意分野です。

各ビット位置をマクロで表すと安全で読みやすくなります。

C言語
#include <stdio.h>
#include <stdint.h>

// 各フラグ(ビット位置)を明示
#define F_READ   (1u << 0) // 0x01
#define F_WRITE  (1u << 1) // 0x02
#define F_EXEC   (1u << 2) // 0x04

int main(void) {
    unsigned flags = 0;
    flags |= F_READ | F_WRITE; // 読み/書きをON

    printf("flags=0x%02X\n", flags);
    printf("READ?  %s\n", (flags & F_READ)  ? "ON" : "OFF");
    printf("WRITE? %s\n", (flags & F_WRITE) ? "ON" : "OFF");
    printf("EXEC?  %s\n", (flags & F_EXEC)  ? "ON" : "OFF");

    // WRITEをOFFにする
    flags &= ~F_WRITE;
    printf("After clear WRITE: 0x%02X\n", flags);
    return 0;
}
実行結果
flags=0x03
READ?  ON
WRITE? ON
EXEC?  OFF
After clear WRITE: 0x01
注意

1 << 31のような符号付き左シフトは未定義動作になり得ます

常に1uなどunsignedを使いましょう。

#defineの注意点と落とし穴

型がない点

#defineには型がありません

そのため、式に混ぜると意図せぬ型変換が起きたり、オーバーフローを招くことがあります。

特にビットマスクではUULLを積極的に使いましょう。

C言語
#include <stdio.h>
#include <stdint.h>

// 悪い例: #define SIGN_BIT_BAD (1 << 31) // 符号付きの左シフトで未定義動作になり得る
#define SIGN_BIT (1u << 31) // 良い例: unsignedで定義

int main(void) {
    uint32_t v = SIGN_BIT; // 0x80000000
    printf("SIGN_BIT hex=0x%08X, as signed=%d, as unsigned=%u\n",
           v, (int32_t)v, v);
    return 0;
}
実行結果
SIGN_BIT hex=0x80000000, as signed=-2147483648, as unsigned=2147483648

同じビット列でも、解釈する型で意味が変わることが分かります。

演算子の優先順位と括弧

マクロの展開結果が元の文脈に埋め込まれるため、意図しない優先順位で評価されることがあります。

定義は常に括弧で包むのが基本です。

C言語
#include <stdio.h>

#define ADD12 1 + 2      // 括弧なし
#define ADD12_P (1 + 2)  // 括弧あり

int main(void) {
    int a = 10 * ADD12;   // 10 * 1 + 2 -> 12
    int b = 10 * ADD12_P; // 10 * (1 + 2) -> 30
    printf("a=%d, b=%d\n", a, b);
    return 0;
}
実行結果
a=12, b=30

括弧を怠ると、バグになっても気付きにくいのが厄介です。

セミコロンは付けない

#define PI 3.14;のように末尾にセミコロンを付けるのは誤りです。

展開先に余分な;が混じり、構文エラーや微妙な挙動を引き起こします。

C言語
// 悪い例
// #define PI 3.14;   // NG: 末尾の ; がそのまま入る

// 良い例
#define PI 3.14

名前衝突とスコープ

マクロのスコープは「定義位置からその翻訳単位の終わりまで」です。

関数スコープに従いません。

標準ヘッダのマクロや他ライブラリのマクロと衝突しやすいため、以下を意識します。

  • プロジェクトやモジュール名のプレフィックスを付ける(例: NET_MAX_CONN)。
  • 一時的に必要なものは使用後に#undefする。
  • 予約済みっぽい名前(EOFminなど)を再定義しない。

デバッグのしづらさ

マクロはプリプロセス後に「消える」ため、デバッガで直接見えません

挙動確認が難しい時は以下が有効です。

  • -Eオプションでプリプロセス後を出力する(GCC/Clang: gcc -E)。
  • マクロを一時的にconstへ置き換えてデバッグする。
  • ログに展開後の値を出すテストコードを用意する。

constやenumの使い分け

ルールの目安を示します。

  • 型安全に扱いたい定数: constを使う。デバッガで追いやすく、関数引数の型とも相性が良い。
  • 配列長やswitchのcaseラベル: enum#define。Cではconst intは整数定数式と見なされず使えない場面があります。
  • 大きなビット定数や64bit値: #defineULLを付けるか、static const unsigned long longなどを使う。enumは多くの処理系でint幅なので不適。
C言語
// 配列長やcaseラベルに向く
enum { MAX_ITEMS = 32 };

static const double TAX_RATE = 0.1; // 型安全に扱いたいならconst
#define FLAG_DEBUG (1u << 0)        // ビットフラグなら#defineが楽

安全性・可読性・用途の三点を踏まえて選択するのがコツです。

まとめ

#defineは「文字列置換」であり、型を持たないという本質を理解すると、定数や簡単な式の管理がぐっと楽になります。

式は常に括弧で包み、unsignedやULLのサフィックスで型を意識し、単位やビットフラグを名前で表現すれば、読みやすく安全なコードになります。

一方でセミコロン禁止、名前衝突、デバッグの難しさなどの落とし穴も忘れず、必要に応じてconstenumとの使い分けを行いましょう。

本記事では引数付きマクロや条件コンパイルは扱いませんでしたが、それらを組み合わせることで、さらに表現力の高いコード設計が可能になります。

まずは定数と簡単なマクロから、正しい書き方を体に染み込ませていきましょう。

C言語 プリプロセッサとマクロ
この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!