1つのソースファイルで全てのプログラムを書くと、大きなプログラムになると大変なことになります。分業で開発するときにも、ファイルが1つだけというのは不便です。今回はソースファイルの分割方法...の前に、その時に必要なリンケージという概念について話したいと思います。
では、今回の要点です。
では、いってみましょう。
グローバル変数と関数は、ファイルを越えて利用することができます。しかし、ファイルを越えて利用させたくないときは、ファイルを越えて利用できないようにすることもできます。こういった、ファイルを越えて利用できるかどうかをリンケージと呼びます。
定義から分かるように、リンケージには2種類しかありません。その2つには外部リンケージ、内部リンケージという名前が付いています。そのファイルの外部でも利用できるものは外部リンケージを、そのファイルの内部でしか利用できないものは内部リンケージを持つと言います。
用語はともかく、C/C++言語にはファイルを越えて利用できるかどうかと言う制限が存在しているわけです。
先ず、外部リンケージから話します。
外部リンケージを持たせるには、型に extern(外部)というのを加えます。しかし、extern は普通省略できます。つまり、普通にグローバル変数や関数を宣言すれば、それは別のファイルでも利用できるわけです。
しかし、変数や関数は何らかの宣言をしなければ使えないと前に言いました。つまり、何らかの宣言を行ってはじめて、そのファイルでも別ファイルの変数や関数を利用できるようになるわけです。
しかし、ここで問題になるのは二重定義の問題です。
ファイル1でグローバル変数 int a; を、ファイル2でもグローバル変数 int a; を定義するとします。すると、同じ名前の変数を2つ定義したので、a と使おうとしてもどちらの a なのか分かりません。なので、二重定義エラーが発生します。これは、変数の宣言が実体の定義を兼ねているからこうなるのです。
このように、変数の時には何でもかんでも extern を省略するわけにはいかないのです。片方に extern をつければ二重定義エラーは出なくなります。extern が付いていれば、初期化しない限り実体は作られません。つまり、1つは extern なしのものを書いておくわけです。
あと、内部変数として extern の付いた変数を宣言することもできます。だたし、その変数はどこかでグローバル変数として宣言されていなければなりません。
関数では、プロトタイプ宣言は実体の定義を兼ねていないので、プロトタイプ宣言をそのまま書けば、別ファイルでも使えるようになります。
まとめると、次のプログラムのようになります。
プログラム | |
---|---|
// Extern1.cpp #include <iostream.h> int a = 2; // 実体 void Func() // 実体 { cout << "a = " << a << endl; } |
// Extern2.cpp #include <iostream.h> void Func(); int main() { extern int a; Func(); a = 5; Func(); return 0; } |
実行結果 | |
a = 2 a = 5 |
小難しい理屈は置いておいて、最後に外部リンケージについてまとめます。
実用的には、これだけ覚えておけば充分でしょう。
次は、内部リンケージについてです。
内部リンケージを持たせるには、型に static を付け加えます。「あれ? static は静的な内部変数を宣言するときに使うんじゃなかったの?」と思うかも知れませんが、内部リンケージを持たせるときにもこの static を使います。
内部リンケージを持たせると、別ファイルでその変数を使うことができなくなります。内部リンケージでは static の省略とかができないので、これ以上あまり説明することはありません。なので、もう次のプログラムを見てやって下さい。
プログラム | |
---|---|
// Intern1.cpp #include <iostream.h> static int a = 2; static void Func1() { cout << "a(1) = " << a << endl; } void Func2() { Func1(); } |
// Intern2.cpp #include <iostream.h> static int a = 9; void Func1() { cout << "a(2) = " << a << endl; } void Func2(); int main() { Func1(); Func2(); a = 5; Func1(); Func2(); return 0; } |
実行結果 | |
a(2) = 9 a(1) = 2 a(2) = 5 a(1) = 2 |
Intern1.cpp では a と Func1 に、Intern2.cpp では a に内部リンケージを持たせました。
さて、main は Intern2.cpp にあるわけですから、main においては Intern1.cpp の a と Func1 は使えないことになります。
で、main には Func1 の呼び出しがありますが、ここでは Intern2.cpp にある、外部リンケージを持った Func1 を呼び出すことになります。
で、この Func1 では a を使おうとしています。この Func1 は Intern2.cpp にあるので、やはり Intern1.cpp の a は使えないことになります。ここでは、Intern2.cpp にある、内部リンケージを持った a のことになります。
以上より、初めの Func1 では a(2) = 9 と表示されるわけです。
で、次は Func2 の呼び出しです。Func2 は Intern1.cpp で定義されていますが、これは外部リンケージを持っています。で、Intern2.cpp では Func2 のプロトタイプ宣言がされています。なので、ここでの Func2 の呼び出しは Intern1.cpp の Func2 を普通に呼び出すことになります。
で、この Func2 では Func1 を使おうとしています。しかし、Intern1.cpp では Func1 は2つ使える可能性があります。1つは Intern1.cpp にある内部リンケージを持った Func1 で、もう1つは Intern2.cpp にある外部リンケージを持った Func1 です。
しかし、この場合内部リンケージが優先されます。Intern1.cpp に static のついていないプロトタイプを書いたところで、やはり内部リンケージを持った Func1 が優先されるのです。
なので、ここでは Intern1.cpp の Func1 が呼ばれます。
で、この Func1 でも a が使われています。これはもうわかりますね。Intern1.cpp の Func1 なので、Intern1.cpp にある a が使われます。
以上より、初めの Func2 では a(1) = 2 と表示されるわけです。
ここで一旦まとめておきます。
で、次に a に5を代入しています。main は Intern2.cpp にあるので、この a は Intern2.cpp の a になります。つまり、Intern1.cpp の a の値は変わらず、Intern2.cpp の a の値だけが5になるわけです。
では、これを確かめてみましょう。
次に Func1 を呼んでいます。これは Intern2.cpp の a を表示します。確かに5に変わっていることが分かりますね。
で、また次に Func2 を呼んでいます。これは Intern1.cpp の a を表示します。確かに数値は2のままで変わっていないことが分かります。
このように、内部リンケージ、外部リンケージという違いは、プログラムの動作に大きな影響を与えるのです。
ちなみに、extern や static のようなものを記憶クラス指定子と呼びます。他には auto と register というのがありますが、ろくに使わないものなので(auto に至っては絶対使わない)ここでは話しません。
そして、extern, static, auto, register で決められるものを、まとめて記憶クラスと呼びます。まぁ、もう用語は嫌でしょうから、これらは覚えておく必要はないでしょう。(自分も用語は嫌いです。)頭の片隅くらいに置いておけばいいでしょうか?
今回はかなりくどい章になってしまいました。もうちょっとすっきり書きたかったのですが、誤解があると困る章なので、ここまでくどくなってしまいました。
では、くどいようですが、今回の要点です。
ああ、要点もくどいです。
次回は、いよいよソースファイルの分割を行います。では、次回まで。
Last update was done on 2000.8.25
この講座の著作権はロベールが保有しています