C言語入門|条件付きコンパイルのしくみ

― 二重インクルードを防ぐための基本テクニック

stdio.h って、よく見るけど実体は何なんだろう?
そう思ったこと、ありませんか?

実は stdio.h や stdlib.h は、
ただのテキストファイル です。
エディタで普通に開いて、中身を読むことができます。

そして、その中身をのぞいてみると、
ほぼ必ず次のような構造になっていることに気づきます。

ヘッダファイルの冒頭にある不思議な記述

#ifndef _STDIO_H_
#define _STDIO_H_

/* stdio.h の本体 */

#endif

最初に #ifndef と #define、
最後に #endif。

「どうして全部がこんなもので囲まれているんだろう?」
ここに、条件付きコンパイルの重要な役割があります。

二重インクルードとは何か

C言語では、同じヘッダファイルを 2回以上インクルード すると、
中に書かれている宣言や定義が 重複 し、
コンパイルエラーの原因になります。

でも、

「同じファイルを2回も include しないでしょ?」

そう思いますよね。
ところが、実際のプログラムでは簡単に起こります。

二重インクルードが発生する典型例

たとえば次のような構成を考えてみましょう。

  • main.c が stdio.h をインクルードしている。
  • sub.c も stdio.h をインクルードしている。
  • main.c が sub.c をインクルードしている。

この場合、

  • main.c → stdio.h
  • main.c → sub.c → stdio.h

という経路で、stdio.h が2回取り込まれることになります。

これは標準ライブラリに限らず、
自作のヘッダファイルでも同じです。

図で見る二重インクルードの問題

図の説明

この図では、

  • 複数のソースファイルが同じヘッダファイルを参照
  • インクルードが連鎖し、同一ヘッダが複数回展開
  • 宣言が重複してエラーになる

という流れを視覚的に表しています。


#ifndef と #define による解決策

そこで使われるのが、
条件付きコンパイルを利用した二重インクルード防止です。

基本の書き方は次のとおりです。

#ifndef マクロ名
#define マクロ名

/* ヘッダファイルの中身 */

#endif

何をしているのか

意味
#ifndef マクロ名マクロが未定義なら続行
#define マクロ名マクロを定義する。
本体初回だけ有効になる。
#endif条件付きコンパイルの終了

stdio.h の場合の動作イメージ

1回目のインクルード
STDIO_H は未定義
→ 本体が有効
STDIO_H が定義される

2回目以降のインクルード
STDIO_H はすでに定義済み
→ 本体はすべて無効

この仕組みによって、
何回インクルードされても安全
になるわけです。

この方法を インクルードガード と呼びます。

自作ファイルでのインクルードガード例

次は、自作ファイルにインクルードガードを入れた例です。
内容はわかりやすいように少し変更しています。

#include <stdio.h>

#ifndef __SAMPLE_MODULE_C__
#define __SAMPLE_MODULE_C__

void show_message(void)
{
    printf("module loaded\n");
}

#endif

この例では、

  • SAMPLE_MODULE_C が未定義なら有効
  • 2回目以降は中身が無視される

という動作になります。

マクロ名の命名ルールと注意点

インクルードガードのマクロ名には、
ある程度の慣習があります。

項目内容
文字すべて大文字
記号ドットはアンダースコアに置換
装飾前後にアンダースコア2つ

suzuki.c → SUZUKI_C → __SUZUKI_C__

注意点

_STDIO_H_ のように
前後がアンダースコア1つの名前 は、
C言語処理系が予約している名前です。

私たちが自作するマクロでは、
必ずアンダースコア2つ を使うようにしましょう。

条件付きコンパイルがここで果たしている役割

この仕組みは、

  • 実行時の条件分岐ではない。
  • コンパイル前にコードを削除している。

という点が重要です。

二重インクルードの防止は、
条件付きコンパイルの最も代表的で安全な使い道
といえます。