C言語の#define
は、コンパイル前に動くプリプロセッサが文字列をそのまま置き換える仕組みです。
コードの可読性や保守性を上げるために、定数や簡単な式を名前で表現できます。
本記事では「定数や簡単なマクロを定義する」ことに焦点を当て、書き方から注意点、落とし穴まで初心者向けに丁寧に解説します。
引数付きマクロや条件コンパイルは別記事で扱います。
#defineとは?
プリプロセッサとマクロの基本
C言語のプリプロセッサは、コンパイル前にソースを走査し、#
で始まるディレクティブを処理します。
#define
はその代表で、「名前」→「置換テキスト」の対応を作ります。
コンパイラが見る前に、名前が置換テキストへと文字通り差し替えられます。
どんな時に使うか
- 数値や文字列の繰り返しを避けて可読性を上げたい時。
- 定義を1箇所で管理し、変更に強いコードにしたい時。
- 配列サイズやビットフラグなど、コンパイル時に確定している値を扱う時。
重要なのは#define
は「型を持たない文字列置換」だという点です。
この性質は強力ですが、後述の落とし穴にもつながります。
マクロ置換のイメージ
置換はあくまでテキスト処理です。
以下のように展開されます。
// 元のコード
#define LEN 3
int main(void) {
int a[LEN + 1]; // LEN は 3 に置き換わる
return sizeof(a); // => sizeof(int) * 4 になる
}
// プリプロセス後のイメージ
int main(void) {
int a[3 + 1];
return sizeof(a);
}
#defineとconstの違い
#defineはプリプロセス段階の置換、constは型を持つ変数です。
違いを整理します。
- 評価時期:
#define
はコンパイル前に展開、const
はコンパイル時に型チェックされ、実体は変数(最適化で埋め込みの場合もある)。 - 型:
#define
は型なし、const
は明確な型を持つ。 - デバッグ:
const
はデバッガで参照しやすいが、#define
は展開されて消える。 - 使える場所: 配列長や
case
ラベルなど「整数定数式」が必要な場面では#define
やenum
が適任。const
は使えない場面があります。
表にまとめます。
観点 | #define | const |
---|---|---|
展開/評価 | プリプロセス段階の文字列置換 | コンパイル時に型チェック、変数として存在 |
型 | なし | あり |
デバッグの容易さ | 低い | 高い |
配列長/caseラベル | 使える | 使えない場合がある |
アドレス取得(&) | 不可 | 可能 |
オーバーヘッド | なし(埋め込み) | 最適化次第(多くは埋め込み) |
安全性重視ならconst、コンパイル時定数やビット定数重視なら#defineやenumという使い分けが基本です。
#defineで定数を作る
基本の書式
#define 名前 置換テキスト
という形で書きます。
末尾にセミコロンは付けません。
複雑な式は括弧で包むと安全です。
// 基本形
#define MAX_RETRY 3
#define APP_NAME "ExampleApp"
#define BUFFER_BYTES (4 * 1024) // 計算式は括弧で包む
数値定数
整数値の定義では桁の見やすさと型に注意します。
10進(123)、16進(0x7B)、8進(0173)が使えます。
Cでは桁区切り(1_000)は使えません。
#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)
浮動小数点定数
浮動小数点にはf
やL
のサフィックスを使い、型を明確にします。
倍精度(double)がデフォルトなので、floatで扱いたい場合はf
を付けます。
#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'
のように定義できます。
文字列は読み取り専用領域に置かれる傾向があるため、ポインタの扱いに注意します。
#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
型サフィックスの付け方
整数や浮動小数点にはサフィックスで型を付けられます。
計算時の型昇格やオーバーフロー回避に有効です。
種別 | サフィックス例 | 例 | 備考 |
---|---|---|---|
unsigned | U | 10U, 0xFFU | 非負の整数 |
long | L | 1000L | long型 |
unsigned long | UL | 1000UL | |
long long | LL | 100000LL | |
unsigned long long | ULL | 1ULL << 40 | 大きなビットマスクなど |
float | f/F | 3.14f | 単精度 |
long double | L | 3.14L | 拡張倍精度 |
<stdint.h>
にはINT64_C(…)
やUINT32_C(…)
のマクロがあり、幅を意識した定数を書くのに便利です。
命名ルール
マクロは大文字スネークケース(例: DEFAULT_TIMEOUT_MS
)が慣例です。
プロジェクト接頭辞を付けると衝突を避けられます(例: APP_MAX_USERS
)。
標準ヘッダに定義済みの名前(EOF
やNULL
)を再定義しないように心がけます。
書く場所
そのファイルだけで使うなら、ソース先頭付近にまとめます。
複数ファイルで共有するなら.h
ヘッダに置き、必要な.c
から#include
します。
スコープは定義位置からファイル末尾までです。限定したい時は#undef
で解除します。
ヘッダの二重インクルード対策(インクルードガード)は重要ですが、詳細は別記事で扱います。
#defineで作る簡単なマクロ
計算式マクロ
引数を取らない式のかたまりを名前にできます。
必ず括弧で全体を包む癖を付けると安全です。
#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
単位マクロ
サイズや時間など、単位変換をマクロで表すと読みやすくなります。
#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
の得意分野です。
各ビット位置をマクロで表すと安全で読みやすくなります。
#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には型がありません。
そのため、式に混ぜると意図せぬ型変換が起きたり、オーバーフローを招くことがあります。
特にビットマスクではU
やULL
を積極的に使いましょう。
#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
同じビット列でも、解釈する型で意味が変わることが分かります。
演算子の優先順位と括弧
マクロの展開結果が元の文脈に埋め込まれるため、意図しない優先順位で評価されることがあります。
定義は常に括弧で包むのが基本です。
#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;
のように末尾にセミコロンを付けるのは誤りです。
展開先に余分な;
が混じり、構文エラーや微妙な挙動を引き起こします。
// 悪い例
// #define PI 3.14; // NG: 末尾の ; がそのまま入る
// 良い例
#define PI 3.14
名前衝突とスコープ
マクロのスコープは「定義位置からその翻訳単位の終わりまで」です。
関数スコープに従いません。
標準ヘッダのマクロや他ライブラリのマクロと衝突しやすいため、以下を意識します。
- プロジェクトやモジュール名のプレフィックスを付ける(例:
NET_MAX_CONN
)。 - 一時的に必要なものは使用後に
#undef
する。 - 予約済みっぽい名前(
EOF
、min
など)を再定義しない。
デバッグのしづらさ
マクロはプリプロセス後に「消える」ため、デバッガで直接見えません。
挙動確認が難しい時は以下が有効です。
-E
オプションでプリプロセス後を出力する(GCC/Clang:gcc -E
)。- マクロを一時的に
const
へ置き換えてデバッグする。 - ログに展開後の値を出すテストコードを用意する。
constやenumの使い分け
ルールの目安を示します。
- 型安全に扱いたい定数:
const
を使う。デバッガで追いやすく、関数引数の型とも相性が良い。 - 配列長やswitchのcaseラベル:
enum
か#define
。Cではconst int
は整数定数式と見なされず使えない場面があります。 - 大きなビット定数や64bit値:
#define
でULL
を付けるか、static const unsigned long long
などを使う。enum
は多くの処理系でint
幅なので不適。
// 配列長やcaseラベルに向く
enum { MAX_ITEMS = 32 };
static const double TAX_RATE = 0.1; // 型安全に扱いたいならconst
#define FLAG_DEBUG (1u << 0) // ビットフラグなら#defineが楽
安全性・可読性・用途の三点を踏まえて選択するのがコツです。
まとめ
#defineは「文字列置換」であり、型を持たないという本質を理解すると、定数や簡単な式の管理がぐっと楽になります。
式は常に括弧で包み、unsignedやULLのサフィックスで型を意識し、単位やビットフラグを名前で表現すれば、読みやすく安全なコードになります。
一方でセミコロン禁止、名前衝突、デバッグの難しさなどの落とし穴も忘れず、必要に応じてconst
やenum
との使い分けを行いましょう。
本記事では引数付きマクロや条件コンパイルは扱いませんでしたが、それらを組み合わせることで、さらに表現力の高いコード設計が可能になります。
まずは定数と簡単なマクロから、正しい書き方を体に染み込ませていきましょう。