導入
実際のC言語の開発では、関数の宣言などを別ファイル(ヘッダファイル)に切り出し、複数のソースファイルから #include して使い回します。ところが、同じヘッダファイルが(直接・間接を問わず)2回 #include されると、そこに書かれた関数の定義などが2回貼り込まれ、「同じものを2回定義した」というコンパイルエラーになってしまいます。これを防ぐ、C言語の昔からの定石がインクルードガードです。
説明
#include は「そのファイルの内容を、その場所にそのまま貼り込む」だけの単純な処理です。もし a.c と b.c の両方が util.h を #include していて、それらが最終的に1つにまとめられる状況では、util.h の内容が2回貼り込まれることがあります。
flowchart TB
h["util.h(関数の定義など)"]
a["a.c: #include util.h"] --> h
b["b.c: #include util.h"] --> h
h --> merged["同じ内容が2回貼り込まれる可能性"]
merged --> g{"ガードがある?"}
g -->|"ある"| ok["2回目はスキップされて安全"]
g -->|"ない"| err["再定義エラー"]
インクルードガードは、ヘッダファイルの内容全体を次の3点セットで囲む書き方です。
#ifndef UTIL_H
#define UTIL_H
// ヘッダの内容(関数の定義など)をここに書く
#endif
仕組みはこうです。1回目に読み込まれたとき、UTIL_H はまだ #define されていないので #ifndef UTIL_H は真になり、中身が採用されると同時に #define UTIL_H が実行され「もう読み込んだ」という印がつきます。もし同じ内容が2回目に読み込まれると、今度は UTIL_H がすでに定義済みなので #ifndef UTIL_H は偽になり、中身はまるごとスキップされます。結果として、同じ定義が2回コンパイルされることがなくなります。
ブラウザの実行環境は単一ファイルなので、複数ファイルへの分割そのものは体験できませんが、「同じ内容が2回貼り込まれた」状況を1つのファイルの中で再現して確かめてみましょう。
#include <stdio.h>
#ifndef GREETING_H
#define GREETING_H
void greet(void) {
printf("こんにちは\n");
}
#endif
// ここから下は「別のファイルからもう一度 #include された」場面を想定しています。
// ガードのおかげで、2回目のブロックはプリプロセッサに丸ごと無視されます。
#ifndef GREETING_H
#define GREETING_H
void greet(void) {
printf("こんにちは\n");
}
#endif
int main(void) {
greet();
return 0;
}
同じ void greet(void) { ... } のブロックを2回書いていますが、2回目は GREETING_H が定義済みなので #ifndef が偽になりスキップされ、greet が2回定義されることはありません。試しにガード(#ifndef/#define/#endif の3行)を外してみると、次のように「同じ関数を2回定義した」というコンパイルエラーになります。
#include <stdio.h>
void greet(void) {
printf("こんにちは\n");
}
void greet(void) {
printf("こんにちは\n");
}
int main(void) {
greet();
return 0;
}
これが、複数ファイルに分けて開発するときに必ずインクルードガードを付ける理由です。ヘッダファイルの中身を書いたら、まずガードで囲む、というのがC言語の定石です。
やってみよう
上の「ガードあり」のコードを実行して こんにちは が1回だけ表示されることを確かめましょう。次に「ガードなし」のコードを実行し、再定義エラーが起きることを確認してください。
演習
CONFIG_H という名前でインクルードガードを作り、その中に #define MAX_USERS 100 を書いてください。そのブロックをそのまま2回貼り付け(2回目は無視されることを確認するため)、main で MAX_USERS を printf で表示してください(結果は 100)。
ヒント1を見る
#ifndef CONFIG_H → #define CONFIG_H → #define MAX_USERS 100 → #endif の順で1セットを作ります。
ヒント2を見る
同じセットをそのままもう一度貼り付けても、2回目は CONFIG_H が定義済みなのでスキップされ、エラーになりません。