あるソースファイルと別のソースファイルで宣言された識別子は、別の有効範囲を持ちます。これをファイル有効範囲と呼びました。別の有効範囲で宣言された識別子は基本的に参照できません。もし別々の有効範囲で同じ識別子を持つオブジェクトがあったとしても、それらは全くの別物として扱われます。
もちろん、参照する方法も用意されています。そうでなければ、ソースファイルを分けた意味がありませんね。ただ、定義されたものが全て参照できてしまったら、それはそれで混乱してしまいます。参照するものとしないものは、プログラマーが正確に制御できます。
参照できるようにする処理を「リンク」(以下「結合」)と呼びます。
「結合」の基本として、「同じ識別子でしか結合されない」というルールがあります。「a」という識別子のオブジェクトと「b」という識別子のオブジェクトが結合されることはありません。しかし、同じ名前ならば全て結合されるというわけではなく、以下で説明するルールにのっとって結合されます。
実際に結合を確認するコードを書いてみましょう。ファイル「test.c」が次のようになっていたとします。
int a = 1; // 外部定義
このとき「main.c」では、「test.c」のオブジェクト「a」を参照するために次のようにします。
#include <stdio.h> #include <stdlib.h> int a; // 仮定義 int main(void) { printf("a = %d\n", a); // a = 1 return EXIT_SUCCESS; }
「main.c」の「a」は、「test.c」の「a」と結合され、同じオブジェクトを表します。この時のポイントは次の3つです。
まず、外部から参照できる識別子はファイル有効範囲を持つ必要があります。「ブロック有効範囲」などの有効範囲を持つ識別子は外部から参照できません。例では、「test.c」の「a」も「main.c」の「a」もファイル有効範囲を持っています。
次に、外部から参照できる識別子は宣言と同時に初期化されている必要があります。このようなオブジェクト識別子の宣言を「外部定義」あるいは単に「定義」と呼びます。「test.c」の「a」は外部定義です。外部定義されたオブジェクト識別子はオブジェクトの本体となって記憶領域が確保されます。
一方、「main.c」の「a」はファイル有効範囲を持ちますが、宣言の時に初期化されていません。この場合は、外部定義のオブジェクトを参照する仮の定義となります。このような宣言を「仮定義」と呼びます。実際に外部定義のオブジェクトを参照するかどうかは、結合時に決まります。
仮定義は実体となるオブジェクトの本体を確保するわけではないので、複数のファイルで宣言をしても構いません。
一方、定義は実体を作りますので、同じ名前の定義が2つ以上あるとエラーになります。試しに「main.c」の「int a;」を「int a = 1;」として仮定義から定義に変更してみると、「a」が複数定義されているとして結合時にコンパイルエラーになります。
仮定義ばかりで定義がどこにも無かった場合はどうなるでしょうか。試しに「test.c」と「main.c」の両方を「int a;」として仮定義にしてみます。するとコンパイルが成功して、実行すると「a = 0」と表示されるはずです。
結合時に変数「a」の仮定義に対する外部の定義が見つからない場合は、「int a;」は仮定義ではなく「0」で初期化されて宣言された変数「a」の定義としてみなされ、オブジェクトの本体が用意されます。従って、両方のファイルにおける「a」の宣言を仮定義にすることで、「test.c」の「a」と「main.c」の「a」は別のファイル有効範囲を持つオブジェクトとなったわけです。
まとめると、次のようになります。
ファイル有効範囲を持つオブジェクトを定義すると、別のファイルから参照できることが分かりました。
ここで、ファイル有効範囲を持つオブジェクトを、別のファイルからは参照できないようにしたいときがあります。そのためには、宣言時に「static」を指定して定義をします。
先ほどの「test.c」の例で、オブジェクト「a」を別のファイルから参照できないように定義してみましょう。
static int a = 1;
これで、「test.c」の「a」は外部から参照できなくなり、「main.c」における「a」の仮定義「int a;」は外部定義が無いものとして扱われます。
これは、ファイル有効範囲を持つ識別子に「static」を付けると内部結合になるからです。「static」が無い場合は外部結合になります。
結合された識別子を持つオブジェクトは、同じ識別子のオブジェクトを参照するようになります。内部結合とは、この結合が翻訳単位の中で行われる結合で、外部結合は翻訳単位を超えて行われる結合です。
結合する識別子を宣言するときに使うキーワードとして、もう1つ「extern」があります。「extern」を指定すると次のようになります。
コードで見てみましょう。ここではどの識別子もファイル有効範囲とします。
int a = 1; // 定義、外部結合 static int b = 2; // 定義、内部結合 extern int a; //(1)外部結合 extern int b; //(2)内部結合 extern int c = 3; //(3)定義、外部結合 extern int d; //(4)外部結合
(1)と(2)は前方に定義の宣言がありますので、それぞれが宣言された時の結合と同じ結合になります。(3)の「c」は前方に宣言はなく、ここで初めて定義されているので外部結合となります。(4)の「d」も「c」と同じで、ここで初めて宣言されているので、外部結合となります。
「extern」はブロック内に宣言するオブジェクトにも適用でき、そのブロック内では外部定義されたオブジェクトを参照できます。
次の例では、変数「a」はブロック内で宣言されているので、「ブロック有効範囲」を持ちますが、外部結合をしているため、外部定義された変数「a」を参照することになります。
int a = 1; // 定義、外部結合
#include <stdio.h> #include <stdlib.h> int main(void) { extern int a; // 外部結合 printf("a = %d\n", a); // a = 1 return EXIT_SUCCESS; }
ここでもルールは同じです。前方に宣言が無いので外部結合になります。こうしておけば、「main」関数でしか外部定義された変数「a」は使わないようになっているので、「main」関数以外の関数で変数「a」をローカルオブジェクトとして使えるようになります。
こういった宣言をする場合は、次の点に注意してください。
Copyright © ITmedia, Inc. All Rights Reserved.