インクルードガードの書き方

こんにちは、めのんです!

前回ちょっと長くなりすぎたのでキリのいいところで中断してしまいました。
今回もヘッダファイルの書き方の続きになります。

ヘッダファイルの情報を増やす

とりあえず前回のおさらいとして、作成したヘッダファイルとそれを#include指令で取り組む固定長メモリプールの実装を再掲載します。
ファイル名としては「fmp.h」と「fmp.c」になります。

// fmp.h

// メモリブロックサイズ
#define FMP_BLOCK_SIZE 256

// メモリブロック数
#define FMP_BLOCK_COUNT 256

void fmp_setup(void);
void *fmp_alloc(void);
void fmp_free(void* p);
// fmp.c
#include <stddef.h>
#include "fmp.h"

// 処理系の最大境界調整要求を持つ型
union max_align_type
{
  long long a;
  double b;
  long double c;
  void* d;
};

// 処理系の最大調整要求
const size_t max_align_size = offsetof(struct { char a; union max_align_type b; }, b);

union node
{
  union max_align_type aligner;
  char array[FMP_BLOCK_SIZE ];  // 固定長配列
  union node* next;
};

// 固定長メモリプールの管理領域
static union node memory[FMP_BLOCK_COUNT];
static union node *top;

// 固定長メモリプールの初期設定
void fmp_setup(void)
{
  const size_t n = sizeof(memory)/sizeof(memory[0]);
  for (size_t i = 0; i < n - 1; i++)
    memory[i].next = &memory[i + 1];
  memory[n - 1].next = NULL;
  top = &memory[0];
}

// 固定長メモリプールからメモリブロックを割り付ける。 
void *fmp_alloc(void)
{
  if (top == NULL)
    return NULL;
  void *r = top->array;
  top = top->next;
  return r;
}

// 固定長メモリプールから割り付けたメモリブロックを解放する。
void fmp_free(void* p)
{
  if (p == NULL)
    return;
  ((union node*)p)->next = top;
  top = p;
}

今回はfmp.cから境界調整に関わるコードをヘッダファイルに移動させようと思います。
この手のコードが必要になることはときどきあるので、再利用できた方が便利だからです。

// fmp.h

// 処理系の最大境界調整要求を持つ型
union max_align_type
{
  long long a;
  double b;
  long double c;
  void* d;
};

// 処理系の最大調整要求
const size_t max_align_size = offsetof(struct { char a; union max_align_type b; }, b);

// メモリブロックサイズ
#define FMP_BLOCK_SIZE 256

// メモリブロック数
#define FMP_BLOCK_COUNT 256

void fmp_setup(void);
void *fmp_alloc(void);
void fmp_free(void* p);
// fmp.c
#include <stddef.h>
#include "fmp.h"

union node
{
  union max_align_type aligner;
  char array[FMP_BLOCK_SIZE ];  // 固定長配列
  union node* next;
};

// 固定長メモリプールの管理領域
static union node memory[FMP_BLOCK_COUNT];
static union node *top;

// 固定長メモリプールの初期設定
void fmp_setup(void)
{
  const size_t n = sizeof(memory)/sizeof(memory[0]);
  for (size_t i = 0; i < n - 1; i++)
    memory[i].next = &memory[i + 1];
  memory[n - 1].next = NULL;
  top = &memory[0];
}

// 固定長メモリプールからメモリブロックを割り付ける。 
void *fmp_alloc(void)
{
  if (top == NULL)
    return NULL;
  void *r = top->array;
  top = top->next;
  return r;
}

// 固定長メモリプールから割り付けたメモリブロックを解放する。
void fmp_free(void* p)
{
  if (p == NULL)
    return;
  ((union node*)p)->next = top;
  top = p;
}

上のように、union max_align_typeとmax_align_sizeの定義をヘッダファイルに移動しました。

インクルードガードを付ける

今回に限ればこれでもいいのですが、もし何らかの理由でfmp.hを2回インクルードしてしまうとコンパイルエラーになってしまいます。
構造体や初期化子付きのオブジェクトの定義が同じ翻訳単位の複数回重複して行うことができないからです。

PHPにはinclude_onceやrequire_onceがあるのでそれを使えばいいのでしょうけど、Cには#includeしかありません。
そこで「インクルードガード」という仕掛けを使って複数回重複して定義が行われることを防ぎます。

とりあえずインクルードガードを追加したfmp.hを貼り付けますね。

// fmp.h
#ifndef FMP_H_
#define FMP_H_

// 処理系の最大境界調整要求を持つ型
union max_align_type
{
  long long a;
  double b;
  long double c;
  void* d;
};

// 処理系の最大調整要求
const size_t max_align_size = offsetof(struct { char a; union max_align_type b; }, b);

// メモリブロックサイズ
#define FMP_BLOCK_SIZE 256

// メモリブロック数
#define FMP_BLOCK_COUNT 256

void fmp_setup(void);
void *fmp_alloc(void);
void fmp_free(void* p);

#endif  // !FMP_H_

fmp.hの最初に#ifndef FMP_H_が、最後に#endifが追加されていますね。
また、#ifndef FMP_H_のすぐあとに#define FMP_H_というマクロの定義があります。
これが「インクルードガード」にあたります。

それでは順に解説していきます。

まず、#ifndefから#endiですが、規格では「条件付き取込み」といいます。
一般的には「条件付きコンパイル」と呼ぶことの方が多いように思います。

#ifndef指令は指定したマクロが定義されていなければ、#endifまでのソースコードが有効になるという意味です。
すぐあとに#define指令でマクロFMP_H_を定義していますので、1回目はソースコードが有効になりますが、2回目以降は無効になります。
(#define指令で定義するマクロはFMP_H_のように中身が無くてもかまいません)

PHPでいえば、

<?php
if (!defined(FMP_H_)) {
  define('FMP_H_', 1);
  ...
}

にあたると思います(実際、このようにすればinclude_onceやrequire_onceを使わなくても2重に取り込むのを防げます)。

インクルードガードのためのマクロの名前は何でもいいのですが、ヘッダファイルの名前から一意に決まるものがいいでしょうね。
私はヘッダファイル名を全部大文字にして、英数字以外を下線にした上で、末尾に下線を付けるようにしています。

Cでは下線で始まって大文字や下線が続く識別子は処理系に予約されているので使ってはいけないことになっています。
_FMP_Hや__FMP_H__のようなマクロ名の付け方をしているコードもよく見るんですけど、ルール違反なのでまねしない方がいいです。

最後の仕上げ

インクルードガードとは直接関係ないのですが、実はもう少しヘッダファイルを修正する必要があります。
max_align_sizeをヘッダファイルの中で定義してしまうと、複数の翻訳単位でfmp.hが使われたときに定義が衝突してしまいます。

この問題を解説するには、fmp.cの中でmax_align_sizeを定義して、ヘッダファイルの中では

extern const size_t max_align_size; 

のように外部結合の宣言を行うだけでもかまいません。

ただ、それよりもmax_align_sizeをマクロにして定数式として扱えるようにした方が何かと便利です。
定数式であればコンパイル時に解決されますので、switch文のcaseラベルや列挙定数、静的記憶域間を持つオブジェクトの初期化子などに使えます。

#define max_align_size  offsetof(struct { char a; union max_align_type b; }, b)

この変更を反映した最終的なヘッダファイルfmp.hは次のようになります。

// fmp.h
#ifndef FMP_H_
#define FMP_H_

// 処理系の最大境界調整要求を持つ型
union max_align_type
{
  long long a;
  double b;
  long double c;
  void* d;
};

// 処理系の最大調整要求
#define max_align_size  offsetof(struct { char a; union max_align_type b; }, b)

// メモリブロックサイズ
#define FMP_BLOCK_SIZE 256

// メモリブロック数
#define FMP_BLOCK_COUNT 256

void fmp_setup(void);
void *fmp_alloc(void);
void fmp_free(void* p);

#endif  // !FMP_H_

今回の解説は以上となります。

前回のソースコードに一部間違いがあったようでコンパイルできませんでした。
大変申し訳ありません。
ソースコードを貼り付ける際に改行がおかしくなったようです。
現在は修正しましたので、もう大丈夫です。

次回の内容はまだ考えていないのですが、もしかすると1日お休みをいただくかもしれません。
その場合はどうかご容赦ください。