前回はプリプロセッサ命令の define 文についてお話しました。
今回は、新しくプロジェクト「0260_precompiler_includeguard」を作り、プリプロセッサ命令での if ~ else と重複したファイルのincludeを防ぐ、一般にインクルードガードと呼ばれる基本的な使い方をお話したいと思います。。
今回は下の4つのファイルをみながら、インクルードガードの例を確認します。4つのファイルはビルドで結合されて、一つの実行可能なファイル(.exe)になります。
依存関係はこんな感じです。
まず、1. common.h (共通のマクロしゃべる関数形式のマクロを定義)のコードです。
#ifndef COMMON_HEADER
#define COMMON_HEADER
#ifdef __cplusplus
extern "C" {
#endif
// キャラクタが話すときの speak 関数宣言
#define speak(CHARACTER,MESSAGE) \
printf("%s : %s\n", CHARACTER, MESSAGE)
#ifdef __cplusplus
}
#endif
#endif // COMMON_HEADERの終了
なにやら、#ifndefや、#ifdef、それにexternといった新しいものが登場しています。
先に、どんな役割のコードかをお話しすると、 この common.h ではキャラクタが話すときの関数形式のマクロ(speak)を定義しています。
つぎに 2. entry.c です。main関数を定義しているプログラム開始場所です。
#include <stdio.h>
#include "common.h" // speak のマクロ関数
#include "plancSpeak.h" // doPlancSpeak関数の宣言
int main() {
speak("システム", "ゲーム起動しました。");
doPlancSpeak();
speak("システム", "ゲーム終了しました。");
}
そして、3. plancSpeak.h の中身です。
#include "common.h" // speak関数が必要
void doPlancSpeak();
最後に 4. plancSpeak.c の中身です。
#include <stdio.h>
#include "plancSpeak.h" // doPlancSpeak関数の宣言
void doPlancSpeak() {
speak("ぷらんく", "こんにちは。");
speak("ぷらんく", "いい天気ですね!");
}
これらを実行したときの結果は下のようになります。
システム : ゲーム起動しました。
ぷらんく : こんにちは。
ぷらんく : いい天気ですね!
システム : ゲーム終了しました
ゲームが起動し、プレイヤーキャラクタが話す、その後ゲームが狩猟するイメージで、文字列が表示されたと思います。
include文で他のファイルを読み込んでいくとき、同じ共通のヘッダーを読み込もうとする時があります。このとき、ヘッダーファイルの中が重複して取り込まれてしまうことになります。
今回の entry.c の例も同じように重複して common.h を取り込んでいます。
#include <stdio.h>
#include "common.h" // speak のマクロ関数#include "plancSpeak.h" // doPlancSpeak関数の宣言
例えば、今回のプロジェクトの例では、
include 文を書いている箇所を、コードで取り込みしていくと、common.h 部分が 2 箇所存在するコードになります。
#include <stdio.h>(の取り込みされたコード。略)
~ common.h の取り込まれたコード ~~ common.h の取り込まれたコード ~void doPlancSpeak();
entry.c のコード続き
このように重複してファイルのインクルードが発生したときに、関数の宣言ではなく、定義を二重取り込みしたときコンパイルエラーとなります。 宣言だけであれば、二重で取り込んでもエラーにはなりません。
具体的にはエラーとなる例は下のようなコードです。インクルードした結果、 下のように2か所以上、同じ関数の定義があるとエラーになっちゃいます。
void testspeak(){
printf("Hellow!");
}
void testspeak(){
printf("Hellow!");
}
関数の宣言が2か所以上あってもエラーにならないです。下みなたいなコードですね。
void testspeak();
void testspeak();
さてさて、このようにinclude で取り込んだ先が重複するときは、 プリコンパイル処理やコンパイル処理が(エラーになったりはしなくとも)無駄に何度も実行されてしまいます。 それを防ぐためには、プリプロセッサ命令で一度読み込んだヘッダーファイルの定義は二重で読み込むのを防いであげるとよさそうですね。
このような二重取り込みを防ぐような処理を一般的にインクルードガードと呼びます。
今回のソースコードでインクルードガードの例が common.h の中で書いてあります。 ソースコードを見ながらお話していきます。
#ifndef COMMON_HEADER
#define COMMON_HEADER
#ifdef __cplusplus
extern "C" {
#endif
// キャラクタが話すときの speak 関数宣言
#define speak(CHARACTER,MESSAGE) \
printf("%s : %s\n", CHARACTER, MESSAGE)
#ifdef __cplusplus
}
#endif
#endif // COMMON_HEADERの終了
1行目に「#ifndef」というプリプロセッサ命令が登場しています。
ifndef 文は endif 文とペアで書きます。
「#ifndef 〇〇」で、〇〇というシンボルがまだdefine宣言されていなければ、#endif までをプリプロセッサ処理を実行する という意味の命令になります。 なお、ifndef という名前は 「If Not Defined」を省略したことから来ていると思われます。
ifndef 命令や、すぐあとに出てくる ifdef 命令は入れ子にすることができる条件分岐のプリプロセッサ命令です。
もしシンボル「COMMON_HEADER」が define されていないなら、1行~13行目まで、プリプロセッサは処理をしてよい、ということになります。
2行目ではシンボル「COMMON_HEADER」を define された状態にしています。
#define COMMON_HEADER
置換する後の文字を何も書いていません。この状態でシンボル「COMMON_HEADER」が定義された、というととになります。
#ifdef __cplusplus
extern "C" {
#endif
3行目~5行目で ifdef ~ endif 文が登場します。 ifdef は シンボル〇〇がすでに定義されていたら、endifまでをプリプロセッサ処理する、という命令となります。
ここで、__cplusplus とは、C++言語でプリコンパイルしようとした時に、あらかじめ定義されているシンボルのことです。 C言語としてプリコンパイルしようとしたときは定義されていません。C++言語とC言語は互換性があり、C++言語でもC言語で作られたプログラムの資材を活かせるようになっています。
ただし、C++言語ではC言語でサポートしていない文法などが登場しますので、そういったC++言語だけで使いたいような文があるときは、シンボル__cplusplus が定義されているかどうかをチェックして、部分的に処理を差し込むようにすることで対応できます。
ですのでC言語としてプリコンパイルしようとしたときは、この「#ifdef __cplusplus」 ~ 「#endif」 の間は処理されません。
7~8行目で、関数型マクロが定義されています。 なお、speak 関数型マクロはちょっと横に長くなってしまったので、途中で「\ + 改行」を入れて6行目と7行目に分かれていますが、一つのdefine文です。 二つの引数、CHARACTER と MESSAGE を持っており、printf 関数に変換(展開)されます。
// キャラクタが話すときの speak 関数宣言
#define speak(CHARACTER,MESSAGE) \
printf("%s : %s\n", CHARACTER, MESSAGE)
例えば、以降のソースコードでは以下のような変換(展開)がされます。
9~11行目は、先ほど同様、C++言語からのみプリコンパイル処理が通るように ifdef で条件分岐しています。C++言語からであれば、10行目はソースコードで生成される対象となります。
#ifdef __cplusplus
}
#endif
1行目のifndef 文のペアとなる endif文が最後に書いてあります。
#ifdef __cplusplus
}
#endif
ご注意となりますが、プリコンパイラ命令は最後に改行が必要です。 この#endif などは特に改行を入れることを忘れやすいので注意しましょう。
common.h のソースコードを1行1行見ていきました。ここで、二重に取り込まれることを防ぐためのインクルードガード処理を最初と最後に記載しています。
#ifndef COMMON_HEADER
#define COMMON_HEADER
/* 一度だけ取り込みたいソースコード部分 */
#endif // COMMON_HEADERの終了
上記のような書き方をしていれば、シンボル「COMMON_HEADER」が一度定義されたら、2度目は条件に合致せず素通りすることになりますね。 このシンボル「COMMON_HEADER」のような名前は、他のヘッダーファイルと重複しないように注意する必要があります。
Visual Studio を使っていれば、Microsoft が推奨している(とおもわれる)「#pragma once」という命令があります。 これをヘッダーの中で使うと、そのファイルパスは2度目は取り込まないという処理ができます。これは処理系によって動作しないことがありますので使うときは少し注意してあげる必要があります。
プリプロセッサ命令はいままでのC言語の文法とは処理されるタイミングや文法が異なるので、ややこしいですね。 でも巨大なプログラムであればあるほど、こういったプリプロセッサ命令を活用して、パフォーマンスやコンパイル時の省力化が工夫されている印象があります。
更新日 | 更新内容 |
---|---|
更新なし |
コメント、ありがとうございます。
ごめんなさい。エラーでうまく送信できませんでした。ご迷惑をおかけします。しばらくおいてから再度送信を試していただくか、以下から DM などでご連絡頂ければと思います。
Twitter:@NodachiSoft_jpお名前:以下の内容でコメントを送信します。よろしければ、「送信」を押してください。修正する場合は「戻る」を押してください
お名前: