Menu

C言語の定数: const、#define、そして enum

C言語で変化しない値に名前を付ける3つの方法 - const キーワード、#define マクロ、enum 定数 - のそれぞれの実体と、どれをいつ選ぶか。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

定数とは、名前と「変化しない」という約束を持った値です。C言語はそのための仕組みを3つ用意していて、内部的には本当に異なるものです - 1つは変数、1つはテキスト置換、1つは整数型です。

そもそもなぜ値に名前を付けるのか

この2行を比べてみましょう:

double area = 3.14159 * r * r;
double area = PI * r * r;

2つ目のほうが良い理由は3つあり、どれも後になって効いてきます。その数値が何を意味するかを語ってくれること。ちょうど1か所にしか現れないので、精度を変えるのが検索ではなく1回の編集で済むこと。そして PI のタイプミスはコンパイルエラーになるのに対し、3.14195 のタイプミスは誤った答えになること。

コードのあちこちに散らばった名前のない数値はマジックナンバーと呼ばれ、それを取り除くことはC言語のプログラムに対して最も安上がりな改善の1つです。

const

const キーワードは変数を読み取り専用としてマークします:

代入のコメントを外すと、コンパイラが拒否します: assignment of read-only variable 'PI'。ビルド時のこのエラーこそが狙いのすべてです。

const 変数は本物の変数です。型を持つので、コンパイラは使い方を検査します。スコープに従うので、関数の中の const は外に漏れません。アドレスを持つので、ポインタを取れます。そしてデバッガがその名前と値を見せてくれます。

const は宣言した場所で初期化しなければなりません - やり直しの機会はありません:

const int limit;       /* エラー(あるいは永遠にゴミのまま): 後から代入することはできない */
const int limit = 50;  /* 正しい */

#define

プリプロセッサを使う代替はマクロです:

これは変数ではありません。コンパイラが動く前に、プリプロセッサがソース中の PI をすべて 3.14159265358979 という文字列に置き換えます。コンパイラはその名前をまったく見ません - だからこそ #define には型がなく、デバッガにも現れないのです。

セミコロンも = もないことに注目してください。マクロは生のテキストなので、このよくある間違いは:

#define MAX_USERS 100;        /* セミコロンに注意 */

int limit = MAX_USERS + 1;    /* 展開結果: int limit = 100; + 1;  -> エラー */

定義とは似ても似つかない行でエラーを生みます。マクロを大文字で命名する慣習は、まさに読み手に置換が起きていると分からせるために存在します。

マクロは慣習的に UPPER_CASE で書かれます。const 変数も同じ考えの代わりを務めるときは、たいていそれに従います。

enum 定数

3つ目の形は、名前の付いた整数の集合を与えてくれます:

値を割り当てない限り 0 から始まって1ずつ増えるので、MON は 0、FRI は 4 です。無名の enum { ... } は、ただの整数定数を作るよくあるイディオムです。

enum 定数には他の2つに対する本物の利点があります。コンパイラが理解するコンパイル時の整数定数であり、デバッガからも見えることです。詳しくは C言語の enum にあります。

どれを使うか

必要なもの使うもの
型があり、スコープがあり、デバッグできる定数const
配列のサイズ、case ラベル、ビットフィールドの幅enum#define
文字列定数#defineconst char *
浮動小数点の定数const double
関連する整数値の集合enum
プリプロセッサが見なければならないもの(#if、インクルードガード)#define

デフォルトは const にすべきです。値がコンパイル前に存在しなければならないときは #define に、コンパイル時の整数や名前の付いたグループが必要なときは enum に手を伸ばしましょう。

これが本当に効いてくるのは配列のサイズです:

C言語の const int読み取り専用の変数であって、コンパイル時の定数式ではありません。関数の内側であれば C99 は int local[const_size] を可変長配列にすることで受け入れますが、ファイルスコープ、struct の中、case ラベルとしては完全に拒否されます。enum#define はどこでも動きます。

(これは C++ との本物の違いの1つで、C++ では const int は定数式です。言語間を行き来するコードはここでつまずきます。)

const とポインタ

constポインタは3通りに組み合わさり、その意味は互いに入れ替えられません。宣言は名前から外側に向かって読みましょう:

const char *p;          /* p は変更してはならない char を指す */
char * const q;         /* q 自体は指し先を変えられない。char は変えられる */
const char * const r;   /* ポインタも char も変えられない */

const char * の形は最もよく書くことになるものです。文字列を変更せずに読むだけの仮引数に対して、これが正しい型だからです:

読み取り専用の仮引数に const を付けてもコストはゼロで、コンパイラが強制してくれる形で契約を文書化できます。どこでもやる価値があります。

文字列リテラルは事実上 const

const が捕まえるために存在する、微妙な罠です:

char *s = "hello";   /* 合法なCだが、s は読み取り専用メモリを指している */
s[0] = 'H';          /* 未定義動作 - 通常はクラッシュ */

文字列リテラルは実行ファイルの読み取り専用セクションに置かれます。そのポインタを通して書き込むと、たいていセグメンテーション違反になります。ポインタを const char *s = "hello"; と宣言すれば、実行時にクラッシュさせる代わりに、コンパイラがその代入を拒否してくれます。

変更可能な文字列が必要なら、配列にコピーしましょう:

よくある質問

C言語で定数はどう宣言しますか?

宣言に const を付けて値を与えます: const double PI = 3.14159;。以降、コンパイラはそれへの代入をすべて拒否します。古くからの代替はプリプロセッサマクロ #define PI 3.14159 で、こちらは代わりにテキスト置換を行います。

C言語の const と #define の違いは何ですか?

const はコンパイラが認識する本物の型付き変数を作るので、型検査され、デバッガに現れ、スコープに従います。#define はコンパイル前に行われる純粋なテキスト置換で、型もスコープもなく、デバッグ中に調べることもできません。コンパイル時定数が必要な文脈でない限り const を選びましょう。

C言語で const 変数を配列のサイズに使えますか?

ファイルスコープの固定長配列には使えません。const int N = 10; int arr[N]; は C99 では可変長配列になります(関数の内側では許され、ファイルスコープや構造体の中では許されません)。本物のコンパイル時のサイズには #define N 10enum { N = 10 }; を使いましょう。

const char *p はどういう意味ですか?

定数の char へのポインタです。p が指す先を変えることはできますが、指している文字を変えることはできません。char * const p はその逆で、変更可能な char への定数ポインタです。const char * const p は両方を固定します。宣言は名前から外側に向かって読みましょう。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める