本文へスキップ
BecomeCoder

C言語コース · 第9章 プリプロセッサ · レッスン38

インクルードガード ― 二重インクルードを防ぐ定石

ブラウザで完結

導入

実際のC言語の開発では、関数の宣言などを別ファイル(ヘッダファイル)に切り出し、複数のソースファイルから #include して使い回します。ところが、同じヘッダファイルが(直接・間接を問わず)2回 #include されると、そこに書かれた関数の定義などが2回貼り込まれ、「同じものを2回定義した」というコンパイルエラーになってしまいます。これを防ぐ、C言語の昔からの定石がインクルードガードです。

説明

#include は「そのファイルの内容を、その場所にそのまま貼り込む」だけの単純な処理です。もし a.cb.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回目は無視されることを確認するため)、mainMAX_USERSprintf で表示してください(結果は 100)。

ヒント1を見る

#ifndef CONFIG_H#define CONFIG_H#define MAX_USERS 100#endif の順で1セットを作ります。

ヒント2を見る

同じセットをそのままもう一度貼り付けても、2回目は CONFIG_H が定義済みなのでスキップされ、エラーになりません。

実際に動かしてみよう

下のエディタにCを書いて「実行」を押すと、ブラウザ内のインタプリタで出力が表示されます。本文の例をそのまま試したり、書き換えたりしてみましょう(scanf を使う回は「標準入力」欄に値を入れてから実行します)。

C/C++ — ブラウザ内で実行

ブラウザ内でC/C++を動かす学習用インタプリタ(JSCPP)を読み込みます(入門向けのサブセットです)。
スクロールして表示された時点でも自動で読み込まれます。