今回のタイトルはやや分かりにくいかもしれません。「非局所オブジェクト」というのは、関数の外で宣言したオブジェクトのことです。いわゆる「グローバル変数」とほぼ同じと考えてください。
さて、関数の外で宣言された「非局所オブジェクト」ですが、static記憶クラス指定子が付いていれば内部結合になることはいうまでもありません。今回話題にするのは、記憶クラス指定子が付いていないデフォルトの状態での結合がどうなるかです。
結論からいうと、非局所オブジェクトが外部結合になるか内部結合になるかは、C言語とC++では異なります。これはC++の黎明期から有るC言語との非互換性のひとつです。
まず、C言語の場合には、明示的にstatic記憶クラス指定子を付けない限り、非局所オブジェクトは必ず外部結合になります。それに対して、C++では、const修飾子が付いた非局所オブジェクトは、デフォルトでは内部結合になります。
では、const修飾子と同じ仲間であるvolatile修飾子はどうかというと、これは結合の種類には影響を与えません。const volatileのように両方の修飾子が付いた場合には、やはりconst修飾子が付いているので内部結合になります。
では、C++では、どのように書けばconst修飾子のついた非局所オブジェクトを外部結合にできるのでしょうか? それは、次のようにします。
0 1 2 |
extern const int foo = 123; |
上のように、明示的にextern記憶クラス指定子を付けることで、const修飾子が付いていても外部結合にすることができます。C言語では、extern記憶クラス指定子は(定義ではなく)宣言だけのために、主にヘッダファイルで使っていたと思いますが、C++では、このように定義の際にもextern記憶クラス指定子を記述する必要が出てきます。
なお、単なる外部結合ではなく、C結合にする場合には、
0 1 2 |
extern "C" const int foo = 123; |
のようにしなければなりません。
ところで、非局所オブジェクトを外部結合にする場合には、通常ヘッダファイルの中でも宣言を行うかと思います。その場合、
0 1 2 3 4 5 6 |
// foo.hpp extern const int foo; // foo.cpp const int foo = 123; |
のように、定義時にはextern記憶クラス指定子を省略することも可能です。ただし、(上の例で言えば)foo.cppからfoo.hppがインクルードされていることが絶対条件です。
これまでの例は比較的簡単でした。しかし、実際にはもっと判断しにくいケースもあります。
0 1 2 3 |
const char s1[] = "abc"; const char* s2 = "def"; |
上のコードに出てくるs1とs2は、それぞれ外部結合でしょうか? それとも内部結合でしょうか?
正解は、s1は内部結合であり、s2は外部結合です。s2は確かにconst修飾子が付いていますが、それはあくまでも参照先の型についてであり、ポインタ型自体がconstで修飾されているわけではありません。ですから、
0 1 2 |
const char* const s3 = "ghi"; |
のようにすれば、s3は内部結合になります。
C言語またはC++のどちらかしか使わなければ、この非互換性に悩まされることはあまりないでしょう。しかし、両方の言語を使い分けなければならないとき、油断していると、ついついはまってしまう落とし穴です。