第70章 仰山のファイル

 ソースファイルは別に1つである必要はありません。今回は、複数のソースファイルを扱う方法について話したいと思います。

 例によって半分はVC++での話になります。現在バージョン6を使っているので、バージョン6での説明になります。二重定義防止についての話は共通の話なので、そこは読み飛ばさないで下さいね。


 では、今回の要点です。


 では、いってみましょう。


 さて、プロジェクトへのファイルの追加、プロジェクトからのファイルの削除については第4章で説明しました。新規作成についても説明しました。

 このときはプロジェクトからファイルを全部なくしてからファイルを追加したり新規作成したりしました。では、ファイルを全部なくさずに追加したり新規作成したりするとどうなるのでしょうか?

 そうです。それでもう複数ソースファイルのプロジェクトのできあがりなのです。第27章でもやったことがあるかもしれませんね。

 しかし、第27章ではソースファイルを2つ作ることはしませんでした。ソースファイルが2つあると、一体どうなるのでしょうか? なんて、前回にもうソースファイルを2つ使って説明しましたね。リンケージさえ分かっていれば、もう話すことはほとんどありません。

 ただ、どこかに main 関数がないといけない...というのは注意する必要もないでしょうか? そうですね。main 関数は1つでなくてはいけない、という方が注意することでしょうか。


 前回の話で気が付いたかも知れませんが、ソースファイルが2つ以上あるとき、それらは別々にコンパイルされます

 片方のソースファイルで宣言を行っても、もう片方のファイルではまた別に宣言を行わないといけないわけです。しかし、それでも二重定義エラーは起こるので、extern を付けたり、プロトタイプのみを書くようにしたりするのでしたね。

 つまり、「ある(外部リンケージの)実体の定義が行われた」という情報は全ファイル中に1個しか存在できませんが、「ある(全く同一の)宣言が行われた」という情報は全ファイル中に何個あっても構わないのです。

 このカラクリを解くカギはリンクという作業にあります。異なるファイルに同じ変数を定義したときの二重定義エラーをよく見ると、コンパイルエラーとは書いておらず、リンクエラーとなっていると思います。

 先ず、コンパイルすると中間ファイルが作成されます。これをオブジェクトファイルと呼びます。ここには、どんな変数が、どんな関数が定義され、それらがどんなリンケージを持っているかが書かれています。

 しかし、コンパイルは各ソースファイル毎に全く独立に行われるため、この段階では二重定義が起こっているのかどうかが全く分かりません。

 で、リンクはこれを束ねて実行ファイルなどに変換する作業です。このときに、どんな変数が、どんな関数が定義されているかをチェックします。で、変数や関数が参照されているところに、リンケージを確認しながら実体の位置を書き込むわけです。異なるファイルに同じ変数を定義したときの二重定義は、この作業を行うときに発生するのです。

 実体がいくつもあれば、このときに位置が確定しません。なので、エラーがでるわけです。単純な話ですね。


 さて、あるファイルである関数を作りました。この関数を別のファイルでも使いたいと思います。そんなときは、第27章で話したようにヘッダファイルを利用すると便利です。(インクルードの機能については再確認しておくことを勧めます。)

 ヘッダファイルに関数のプロトタイプや、extern 指定の変数宣言を書いておくわけです。それをインクルードすれば、いちいち毎回プロトタイプなどを書かずにすむわけですね。

 同じように、ヘッダファイルにはよく使うマクロの定義なども書いておくと便利でしょう。あとは、const 定数や、インライン関数を書いておくといいでしょう。const 定数やインライン関数は内部リンケージになるので、定義をヘッダファイルに書いて公開します。(注:C言語では const 定数も外部リンケージです。)これらは(普通は)コンパイル時にデータが埋め込まれ、リンクは行われないからです。

 こうやっていろいろ宣言や定義を書いていくと、ある問題が起こってきます。それは二重定義の問題です。これはリンクの段階での二重定義ではなく、コンパイルの段階での二重定義です。

#define ELEM(array)   (sizeof (array) / sizeof *(array))
#define ELEM(array)   (sizeof (array) / sizeof *(array))

というやつです。ELEM を2回定義しようとしたので、二重定義になります。

#define ELEM(array)   (sizeof (array) / sizeof *(array))

とだけ書いてあるヘッダファイルを2回インクルードするとこうなります。ヘッダファイルでヘッダファイルをインクルードするということもあり、こういう事をしていると「いつの間にか2回インクルードしてしまっていた」ということも結構起こります

 なので、ヘッダファイルには必ず二重定義防止コードを書くことになっています。お、文字がでかいです。ここはとても重要です。2回目以降のインクルードの時には、インクルードを行わないようにすることができるのです。

 それには #ifndef と #define と #endif を使います。おや、見慣れないものが出てきましたね。これら、先頭に # の付いた命令プリプロセッサディレクティブ(プリプロセッサ指令)と呼びます。

 #include や #define を見てもらえれば分かりますが、これらはコンパイラがソースファイルを解析する前にいろいろ処理したいときに使います。処理するもの(プロセッサ)が処理を行う前(プリ)に送る指令(ディレクティブ)だから、プリプロセッサディレクティブなわけです。しかし、実際にはコンパイラに対する指令と考えても構いません。実際にそういうプリプロセッサディレクティブも存在します。

 で、これらをどう使えば二重定義を防げるかですが、先ずは次のプログラムを見て下さい。

プログラム
// DblDef1.h
#ifndef DBLDEF1_H_fjds3l3kfj28adl9ja_INCLUDED
#define DBLDEF1_H_fjds3l3kfj28adl9ja_INCLUDED

#define ELEM(array)   (sizeof (array) / sizeof *(array))

#endif // #ifndef DBLDEF1_H_fjds3l3kfj28adl9ja_INCLUDED
// DblDef1.cpp
#include "DblDef.h"
#include "DblDef.h"

int main()
{
    return 0;
}

 どうでしょうか? 実際にコンパイルしてみて下さい。二重定義が出ないでしょう?

 次に、DblDef1.h の初めの方の #define 文をコメントアウトしてみて下さい。こうしてコンパイルすると二重定義の警告が出るはずです。

 この赤で書かれた部分が二重定義防止コードなわけです。簡単に書くと、次のようになります。

#ifndef <マクロ名>
#define <マクロ名>

... ここに定義を書く ...

#endif

 #ifndef 〜 #endif で囲まれた部分は、#ifndef の後にかかれた名前が定義されていなければコンパイルされ定義されていればコンパイルされません。ifndef は if not defined(もし定義されていなければ)の略ですね。

 実際にインクルードを展開したコードを書いてみると、以下のようになります。

// DblDef1.cpp
#ifndef DBLDEF1_H_fjds3l3kfj28adl9ja_INCLUDED
#define DBLDEF1_H_fjds3l3kfj28adl9ja_INCLUDED

#define ELEM(array)   (sizeof (array) / sizeof *(array))

#endif // #ifndef DBLDEF1_H_fjds3l3kfj28adl9ja_INCLUDED
#ifndef DBLDEF1_H_fjds3l3kfj28adl9ja_INCLUDED
#define DBLDEF1_H_fjds3l3kfj28adl9ja_INCLUDED

#define ELEM(array)   (sizeof (array) / sizeof *(array))

#endif // #ifndef DBLDEF1_H_fjds3l3kfj28adl9ja_INCLUDED

int main()
{
    return 0;
}

 1回目の #ifndef では、後続の名前は定義されていないので、#endif までを無事コンパイルできます。で、その中で「後続の名前」のマクロを定義しています。すると、2回目の #ifndef では後続の名前は定義されていて、#endif までは無視されます。

 このようにして、二重定義を防ぐことができるのです。

 注意する点は、「定義する名前は各ファイル毎に変える」という点です。それだけではなく、この名前はここでしか定義されないようにして下さい。上で名前の中にランダムな文字を入れているのは用心のためです。

 まぁ、仕組みがよく分からないなら、丸覚えしてしまって下さい。分からないから使わないということだけは勘弁して下さい。

 あと、最後に注意しておきますが、この二重定義防止コードはコンパイル時の二重定義を防止するだけです。リンク時の二重定義までは防止できないので、こういった二重定義が防止できなかったからといって文句を言うのはやめて下さいね。


 今回はこれで終わりです。では、今回の要点です。


 何だか最近文字と用語が多くていけませんね。次回からは少し簡素にしたいものですが...どうなることやら。(_ _;

 それでは、次回まで。


第69章 リンケージ | 第71章 借りたら返す

Last update was done on 2000.7.28

この講座の著作権はロベールが保有しています