C言語の#define
は、定数や簡単な置換(マクロ)を宣言するためのプリプロセッサ機能です。
プログラムの可読性を高め、値の一元管理や繰り返しの削減に役立ちます。
本記事では定数と引数なしの簡単なマクロに絞って、基本から実践的な書き方、注意点まで丁寧に解説します(引数付きマクロや条件コンパイルは別記事で扱います)。
#defineとは
マクロの基本と仕組み
#defineはコンパイル前(プリプロセス段階)に行われる「文字列の置換」です。
コンパイラがソースコードを解析する前に、プリプロセッサが#define
で定義した名前を対応する文字列に機械的に置き換えます。
したがって、実行時にメモリを消費する変数ではなく、型も持ちません。
マクロの振る舞いの要点は次のとおりです。
多くは「便利さ」と「生の置換ゆえの落とし穴」の両面を持ちます。
- 置換はソースコードのテキストとして行われます。演算の優先順位や結合を誤ると意図しない計算になることがあります。
- 作用範囲は定義以降(同一翻訳単位)で有効です。ヘッダに置けば複数ファイルで使えます。
- マクロ自体はデバッガで「変数」として参照できません。エラー時の表示も展開後のコードに由来します。
#defineとconstの違い(要点)
「定数値」を表したいだけならconst
変数も選択肢です。
違いを把握して使い分けると安全です。
観点 | #define | const |
---|---|---|
型 | なし(単なるテキスト) | あり(型安全) |
評価時期 | プリプロセス時 | コンパイル時/実行時(最適化で埋め込み可) |
デバッグ | 値の追跡が難しい | 変数として追跡しやすい |
範囲 | 翻訳単位全体(定義以降) | 通常のスコープ規則が適用 |
用途例 | マジックナンバーの排除、条件コンパイルのフラグ | 型が重要な定数、インターフェース上の定数 |
本記事は#defineに焦点を当てますが、安全性を優先したい場面ではconstやenumの利用も検討してください。
定数と簡単な置換の使いどころ
アプリ全体で使う固定値(バッファサイズやファイル拡張子、アプリ名など)を1か所で定義して全体から参照できるのが最大の利点です。
プロジェクトの途中で値を変更しても、再コンパイルだけで一括反映されます。
また、簡単な「別名」を作る用途(改行をNL
とするなど)でも可読性が上がります。
なお、引数付きの関数風マクロや条件コンパイル(#if
や#ifdef
)、インクルードガードなどは別記事で扱います。
#defineで定数を定義する書き方と例
基本構文
定義はとてもシンプルです。
等号(=)やセミコロン(;)は不要です。
#define マクロ名 置換テキスト
例えば次のように書きます。
// 基本的な定数マクロ
#define PI 3.14159265358979323846 // 円周率の近似
#define APP_NAME "MacroSample" // アプリ名(文字列)
#define BUFFER_SIZE 1024 // バッファサイズ(バイト)
マクロ名の直後は空白(スペースやタブ)が必要です。
また、長い定義はバックスラッシュで行継続できます。
// 行継続の例(最後の行以外の末尾に \ を置く)
#define LICENSE_TEXT \
"This software is provided 'as-is', " \
"without any express or implied warranty."
数値定数マクロの例
円の周長を計算する簡単な例です。
浮動小数点定数や整数定数の接尾辞(U, L, ULなど)もコメントで軽く触れます。
#include <stdio.h>
// 数値定数マクロ
#define PI 3.14159265358979323846
#define RADIUS 5.0
#define MAX_USERS 100U // Uはunsignedの目安(型安全はconstの方が確実)
// 2πrで周長を計算
int main(void) {
double circumference = 2.0 * PI * RADIUS; // マクロはそのまま置換される
printf("RADIUS=%.1f, circumference=%.3f\n", RADIUS, circumference);
printf("MAX_USERS=%u\n", MAX_USERS);
return 0;
}
RADIUS=5.0, circumference=31.416
MAX_USERS=100
文字列定数マクロの例
文字列リテラルは隣接させると結合されます。
これを利用してアプリ名とバージョンを組み立てます。
#include <stdio.h>
#define APP_NAME "MacroSample"
#define APP_VER "1.2.3"
#define APP_FULL_NAME APP_NAME " v" APP_VER // "MacroSample v1.2.3"
int main(void) {
printf("App: %s\n", APP_FULL_NAME);
return 0;
}
App: MacroSample v1.2.3
式で定義する定数マクロの例
演算子の優先順位で意図が変わらないよう、式は必ず括弧で囲むのが基本です。
#include <stdio.h>
#define SECONDS_PER_MIN (60)
#define MINUTES_PER_HOUR (60)
#define HOURS_PER_DAY (24)
#define SECONDS_PER_DAY (HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MIN)
#define KB (1024)
#define BUFFER_CAPACITY (32 * KB) // 32KB
int main(void) {
printf("SECONDS_PER_DAY=%d\n", SECONDS_PER_DAY);
printf("BUFFER_CAPACITY=%d bytes\n", BUFFER_CAPACITY);
return 0;
}
SECONDS_PER_DAY=86400
BUFFER_CAPACITY=32768 bytes
括弧を省くと誤った計算になる典型例です。
#include <stdio.h>
// 悪い例: 括弧なし
#define DEC_BAD 1 - 1
// 良い例: 全体を括弧で囲む
#define DEC_OK (1 - 1)
int main(void) {
int a = 10 * DEC_BAD; // 10 * 1 - 1 に展開 => 9
int b = 10 * DEC_OK; // 10 * (1 - 1) に展開 => 0
printf("a=%d, b=%d\n", a, b);
return 0;
}
a=9, b=0
式マクロは常に括弧で守るという習慣をつけると、バグを大幅に減らせます。
簡単なマクロ(引数なし)の書き方
別名を作るマクロの例
関数名やフォーマット文字列、改行文字に短い別名を与えると、意図が読み取りやすくなることがあります。
ただし、関数の別名は過度に使うと可読性を落とすため控えめにしましょう。
#include <stdio.h>
// 短い別名
#define NL '\n' // 改行文字の別名
#define FMT_INT "%d" // 整数のprintfフォーマット
#define PRINTF printf // printfの別名(推奨しすぎない)
// 文字列連結でフォーマットを組み立てる例
int main(void) {
int value = 42;
PRINTF("value=" FMT_INT "%c", value, NL); // "value=42\n"
return 0;
}
value=42
型の別名を作るときは#defineではなくtypedef
が推奨です。
#define U64 unsigned long long
のような型エイリアスはトラブルの元になりやすいため避けましょう。
複合式マクロと括弧のコツ
ビットフラグの組み合わせのように、複数の定数を組み合わせた「合成値」も引数なしマクロで定義できます。
演算の都合で「外側の式」と混ざっても安全なように、各構成マクロを括弧で囲むのがコツです。
#include <stdio.h>
#include <stdbool.h>
// 個別フラグ
#define FLAG_READ (1u << 0)
#define FLAG_WRITE (1u << 1)
#define FLAG_EXEC (1u << 2)
// 合成フラグ(必ず括弧で囲む)
#define FLAG_RW (FLAG_READ | FLAG_WRITE)
#define FLAG_ALL (FLAG_READ | FLAG_WRITE | FLAG_EXEC)
// フラグチェック用の関数(ビットANDで評価)
static bool has_flag(unsigned v, unsigned f) { return (v & f) == f; }
int main(void) {
unsigned perm = FLAG_RW; // 読み書き
printf("perm=0x%02X\n", perm);
printf("has RW? %s\n", has_flag(perm, FLAG_RW) ? "yes" : "no");
printf("has EXEC? %s\n", has_flag(perm, FLAG_EXEC) ? "yes" : "no");
// 括弧がないと、(perm & FLAG_READ | FLAG_WRITE)のように誤解釈され得る
return 0;
}
perm=0x03
has RW? yes
has EXEC? no
使い方のコツと注意点
命名規則
マクロは変数と違い「型がない」ため、ひと目でマクロだとわかる命名が有効です。
- 一般的には全大文字+スネークケース(例:
MAX_BUFFER_SIZE
)を用います。 - 衝突防止に接頭辞を付けます(例:
APP_
、MY_
)。 - 実装予約の識別子(先頭がアンダースコア+大文字、または二重アンダースコア)は使わないでください。例:
_X
、__X
は避ける。
セミコロン不要と空白の扱い
#defineの行末にセミコロンは不要です。
#define PI 3.14;
のように付けると、展開先に余計な;
が入り文法エラーやバグになります。
マクロ名の直後には1つ以上の空白が必要で、長い置換テキストは\
で改行を継続できます。
// 悪い例: セミコロンが展開先に混入する
#define PI_BAD 3.14;
// 良い例
#define PI_GOOD 3.14
// 良い例: 長文を \ で継続
#define MSG_LONG "Line1 " \
"Line2 " \
"Line3"
定義する位置
使う場所より前で定義する必要があります。
ファイル内だけで使うならそのソースの先頭付近に、複数ファイルで共有するならヘッダファイル(.h)に置くのが慣例です。
ヘッダの二重インクルード対策(インクルードガード)は別記事で説明します。
#undefで定義を解除する
一度定義したマクロは#undef
で解除できます。
必要に応じて再定義も可能ですが、再定義は混乱の元なので最小限に留めます。
#include <stdio.h>
#define GREETING "Hello"
int main(void) {
printf("%s\n", GREETING); // -> Hello
// いったん解除して再定義
#undef GREETING
#define GREETING "Hi"
printf("%s\n", GREETING); // -> Hi
return 0;
}
Hello
Hi
外部のヘッダが同名マクロを定義している場合の衝突回避として#undef
を使う場面もありますが、基本は一貫した命名と定義場所の整理で避けるのが理想です。
まとめ
#defineは「コンパイル前のテキスト置換」で定数や簡単な置換を実現します。
扱いが軽くて便利な反面、型がなく作用範囲も広いため、以下の基本を徹底すると安全です。
まず、式は必ず括弧で囲む
。
次に、セミコロンは付けない
。
そして、わかりやすい命名(全大文字+接頭辞)
で意図を明確にします。
広く使う値はヘッダにまとめ、必要に応じて#undef
で整頓してください。
なお、引数付きマクロや条件コンパイルなどの応用は別記事で掘り下げます。
今回紹介した基礎を確実に身につけることで、初心者の方でも読みやすく保守しやすいCコードを書けるようになります。