2014年09月18日
#include_nextの疑問メモ
前回の記事で、GNU CPP の独自拡張 #include_next 指令を紹介しました。
「GNU CPP(GCC)の独自拡張#include_next指令」
このドキュメントは簡単な概要と例だけなので、ちょっと複雑なケースになると、この挙動は仕様なのか?バグなのか?という判断が難しくなります。
この間、私が悩んだ疑問は、過去に以下の ML で取り上げられた疑問とほぼ同じものでした。
gcc-help ML: #include_next: wrong search order?
この疑問を単純化して解説した後、私なりの解釈を説明します。
「GNU CPP(GCC)の独自拡張#include_next指令」
このドキュメントは簡単な概要と例だけなので、ちょっと複雑なケースになると、この挙動は仕様なのか?バグなのか?という判断が難しくなります。
この間、私が悩んだ疑問は、過去に以下の ML で取り上げられた疑問とほぼ同じものでした。
gcc-help ML: #include_next: wrong search order?
この疑問を単純化して解説した後、私なりの解釈を説明します。
まず、GNU CPP は、#include や #include_next 指令を見つけると、gcc-include-dir(GCC 独自のインクルードファイルが含まれるディレクトリ)、sys-include-dir(システムのインクルードファイルが含まれるディレクトリ)の順にディレクトリを検索するとします。それぞれのディレクトリには、以下のようなファイルが含まれています。
最初、私は、
しかし、実際に組み込まれたファイルは、以下のようになりました。
いずれにせよ、ディレクトリの検索優先度がシステムヘッダ置き場よりも高い場所に置かれた自前の limits.h から、システムの limits.h を、無限ループにならずにインクルードはできているので、目的は達成できているのですが、なんとなくモヤモヤします。もしかして、GNU CPP のバグなのでしょうか?(最初に紹介した ML のやり取りでは、最後はうやむやのまま終わったようで、GCC 3.3.3 の GNU CPP のバグでは?という結論でした。)
とはいえ、GCC 4.9.0 でも挙動は同じですし、そもそも limits.h のコメントやインクルードガードの書き方が、2 回インクルードされることを想定している感じなので、バグとも考えにくいです。
結論から言いますと、これは正しく #include_next の仕様通りの挙動のようです。
ポイントは、カレントファイル(#include_next 指令行が記述されたファイル)が、何番目の検索パスで見つかったファイルなのか?ということです。
私が納得した検索手順の説明は、以下のようになります。
- gcc-include-dir
limits.h
syslimits.h
- sys-include-dir
limits.h
少し見辛いですが、各ファイルの内容は以下の通りです。(不要なインクルードガードや定義等は全て省略しています。)
gcc-include-dir
limits.h ----- ここから -----
#ifndef _GCC_LIMITS_H_
#define _GCC_LIMITS_H_
/* Use "..." so that we find syslimits.h only in this same directory. */
#include "syslimits.h"
#else
#include_next <limits.h>
#endif
----- ここまで -----
syslimits.h ----- ここから -----
#include_next <limits.h>
----- ここまで -----
sys-include-dir
limits.h ----- ここから -----
----- ここまで -----
この状態で、適当な C ファイルで #include <limits.h> すると、どの順番にファイルが組み込まれることになるでしょうか?最初、私は、
#include <limits.h>
gcc-include-dir/limits.h
#include "syslimits.h"
gcc-include-dir/syslimits.h
#include_next <limits.h> ※1
sys-include-dir/limits.h
という順番になると考えました。※1 の #include_next で、最初に見つかった gcc-include-dir の limits.h ではなく、次に見つかる sys-include-dir の limits.h が採用されると考えたからです。しかし、実際に組み込まれたファイルは、以下のようになりました。
#include <limits.h>
gcc-include-dir/limits.h
#include "syslimits.h"
gcc-include-dir/syslimits.h
#include_next <limits.h> ※1
gcc-include-dir/limits.h
#include_next <limits.h> ※2
sys-include-dir/limits.h
※1 の #include_next では、なぜか 2 番目の sys-include-dir ではなく、1 番目の gcc-include-dir の limits.h が組み込まれ、※2 の #include_next で、ようやく 2 番目の limits.h が組み込まれました。これは何故でしょうか?いずれにせよ、ディレクトリの検索優先度がシステムヘッダ置き場よりも高い場所に置かれた自前の limits.h から、システムの limits.h を、無限ループにならずにインクルードはできているので、目的は達成できているのですが、なんとなくモヤモヤします。もしかして、GNU CPP のバグなのでしょうか?(最初に紹介した ML のやり取りでは、最後はうやむやのまま終わったようで、GCC 3.3.3 の GNU CPP のバグでは?という結論でした。)
とはいえ、GCC 4.9.0 でも挙動は同じですし、そもそも limits.h のコメントやインクルードガードの書き方が、2 回インクルードされることを想定している感じなので、バグとも考えにくいです。
結論から言いますと、これは正しく #include_next の仕様通りの挙動のようです。
ポイントは、カレントファイル(#include_next 指令行が記述されたファイル)が、何番目の検索パスで見つかったファイルなのか?ということです。
私が納得した検索手順の説明は、以下のようになります。
#include <limits.h> ※ gcc-include-dir で発見、採用。
gcc-include-dir/limits.h
#include "syslimits.h" ※ "..." なので、まずはカレントディレクトリ(.)から検索、発見。
./syslimits.h ※ カレントファイルは gcc-include-dir/syslimits.h ではなく、./syslimits.h なのがポイント。
#include_next <limits.h> ※ カレントディレクトリで発見、スルー。次の gcc-include-dir で発見、採用。
gcc-include-dir/limits.h
#include_next <limits.h> ※ gcc-include-dir で発見、スルー。次の sys-include-dir で発見、採用。
sys-include-dir/limits.h
ちなみにこの gcc-include-dir 以下の limits.h や syslimits.h は GCC のビルド時(configure 時)に、install-tools 以下の gsyslimits.h や include/limits.h がコピーされたもののようです。(GCC のビルド時に、システムの limits.h を変更する必要が無い場合の挙動。)GCC はこのようなトリックで、もし必要ならば、ビルド時に float.h や limits.h を自分に都合が良いように修正するようです。