相互インクルード?循環インクルード?

「よくやってしまう」ほどではないけど、昨日の分も含めて過去に
2回ほどやってしまった事がある気がする。

ちょっとしたうっかりミスと、インクルードする順番が違う時に限って起こる。

症状

自分で定義した型のデータを作れない。コンパイラエラーがでる。
エラーメッセージによると多分、コンパイラが自分で定義した型名を、型名だと思ってない。
BCC5.5だと
>エラー E2139 header2.h 8: 宣言に ; がない
とか、
>エラー E2141 header2.h 12: 宣言の構文エラー
などと表示されると思う。

/* header1.h */
#ifndef __header1_h__
#define __header1_h__

/* ぼーっとしていて、意味も無くincludeしちゃった */
#include "header2.h"

typedef int h1_type;

#endif /* __header1_h__ */
/* header2.h */
#ifndef __header2_h__
#define __header2_h__

#include "header1.h"

struct st_aiueo{
	/* header1.hにあるデータ型 */
	h1_type data;
};

#endif /* __header2_h__ */
/* include_each_other.h */

#if 0 /* うまくいくパターン */
# include "header2.h"
# include "header1.h"
#else /* いかないパターン */
# include "header1.h"
# include "header2.h"
#endif

int main(void)
{
	/* Do nothing. */
	return 0;
}

何が悪いか

BCC5.5がある人は、cpp32 <ソースファイル名> としてみると、
ヘッダファイルが結合された形で出力されます。
が、やってみたら、行頭に/* 元々のファイル名 */
がついて見づらかったので手動でまとめてみました。

/* #includeを使わずに、全部まとめてみた */

/* header2.h にあった内容 */
struct st_aiueo{
	/* header1.hにあるデータ型 */
	h1_type data;
};

/* header1.h にあった内容 */
typedef int h1_type;

int main(void)
{
	/* Do nothing. */
	return 0;
}

もし二重(多重)インクルード防止コードが無かったら
無限に互いをインクルードするのかもしれませんが、その点は大丈夫です。
上でまとめて書いたコードのように、まず最初にheader2.hの内容がくるので、
header1.hで定義されているh1_typeというデータ型はまだ定義されていないという事になります。
これはheader1.hが、まずheader2.hをインクルードしてるからです。
(そしてheader2.hは多重インクルード防止コードにより、事実上header1.hをインクルードできない。)

まとめ

例のやつで、もしinclude_each_other.cが2つのヘッダファイルをインクルードする順番が違えば、
この問題は起こりません。それが厄介なところです。
これはうっかりミスですが、設計上そうしなくちゃいけない事なんてあるんだろうか…。
(お互いがお互いを必要とするような場合とか?)まあ難しい話はわかりませんけどね。