ふとexternの扱いについて勉強していたんですが、
もしヘッダーに宣言する場合は変数はexternを使うと思うんですが、
以下の様に書いてみると、
コンパイルがとおり、hoge.cとmain.cでは変数hogeは同じでした。
しかし、CファイルをCPPファイルに変更すると、
リンクできなくなり、「複数回定義されている」とエラーがでてきました。
ヘッダーの方はexternをつけないといけないと思っていたのですが、
CPPファイルだとちゃんとエラーがでるのにCファイルだとリンクできて実行もできてしまう。
これは、なぜなんでしょう。Cファイルではexternが省略されているのでしょうか。
どなたかご教授おねがいします。
ちなみに環境は
・Windows7
・VisualStudio2010Professional
です。
ヘッダーファイルでの変数について
Re: ヘッダーファイルでの変数について
C言語とC++で決定的に異なるのが、namespaceの存在と仮定義の有無です。externは関係ありません。
C言語にはnamespaceの概念がなく、仮定義があります。一方でC++にはnamespaceがあり、仮定義がありません。どういったことでしょう?
namespaceは分かりやすいですね。
これは複数のファイルに同じ名前のnamespaceがあっても構わないわけですが、それらはC言語で言う1つの繋がったファイルのような名前空間となります(外部リンケージを持つ変数等に限ります*註)。そして、C++では、1つのnamespaceのなかに同じ宣言を2つ書いた場合、エラーとなります。
ところで、namespaceを書かなかった場合の扱いですが、全てがグローバルのnamespaceとなります。
グローバルなnamespaceの中で、同じ変数名で定義しようとするとエラーが起きます。これはC++で問題のプログラムがコンパイルできない原因だと考えられます。C++では(externなど付けなければ)書いたら定義されます。
一方で、C言語の場合は仮定義というものが存在しています。これは、externなどのストレージクラス指定子と初期化指定子(=で初期値を指定)がない定義です。
仮定義は、1つのファイルの(翻訳単位の)コンパイルが終わるまでに実際の定義が現れなかった場合、実際の定義となります。この際は0で初期化されます。
2つの定義を一緒に書くことはできません。
このように初期化なしでexternやstaticを指定しないと仮定義になります。
更に、何も指定しなくてもstaticが指定されていないグローバル変数は常に外部リンケージを持ちます。つまりどのファイルからも可視状態です。仮定義は、他のファイルで定義されていればextern付きの「定義」ではなく「宣言」として扱われます。ただし、他のファイルに仮定義が存在している場合はどうなるかコンパイラ依存です。VC++の場合は共有されます。
link : http://msdn.microsoft.com/ja-jp/library/k8w8btzz.aspx
// main.c
int x; // extern int x = 0;として扱われる
// sub.c
int x; // extern int x; として扱われる
なお、externは同じファイル内に既に宣言がある場合、それに従います。つまり、extern宣言(=なしのものです)は、定義はしないで宣言だけすることを明示します。externは仮定義にはさせません!
※ 2/28 ご指摘の通り、私の理解が間違っていたので修正させて頂きました。(打ち消し線のものは古い文章で、下線のものは追加した文章です。)
C言語にはnamespaceの概念がなく、仮定義があります。一方でC++にはnamespaceがあり、仮定義がありません。どういったことでしょう?
namespaceは分かりやすいですね。
これは複数のファイルに同じ名前のnamespaceがあっても構わないわけですが、それらはC言語で言う1つの繋がったファイルのような名前空間となります(外部リンケージを持つ変数等に限ります*註)。そして、C++では、1つのnamespaceのなかに同じ宣言を2つ書いた場合、エラーとなります。
オフトピック
*註
同じ名前空間であっても、内部リンケージを持つような宣言、例えばstaticがついていたりconstが付いていたりした場合は、そのファイル内でのみ参照されます。従って別のファイルで定義してもそれぞれ別と見なすのでエラーになりません。
同じ名前空間であっても、内部リンケージを持つような宣言、例えばstaticがついていたりconstが付いていたりした場合は、そのファイル内でのみ参照されます。従って別のファイルで定義してもそれぞれ別と見なすのでエラーになりません。
グローバルなnamespaceの中で、同じ変数名で定義しようとするとエラーが起きます。これはC++で問題のプログラムがコンパイルできない原因だと考えられます。C++では(externなど付けなければ)書いたら定義されます。
一方で、C言語の場合は仮定義というものが存在しています。これは、externなどのストレージクラス指定子と初期化指定子(=で初期値を指定)がない定義です。
仮定義は、1つのファイルの(翻訳単位の)コンパイルが終わるまでに実際の定義が現れなかった場合、実際の定義となります。この際は0で初期化されます。
2つの定義を一緒に書くことはできません。
このように初期化なしでexternやstaticを指定しないと仮定義になります。
更に、何も指定しなくてもstaticが指定されていないグローバル変数は常に外部リンケージを持ちます。つまりどのファイルからも可視状態です。仮定義は、他のファイルで定義されていればextern付きの「定義」ではなく「宣言」として扱われます。ただし、他のファイルに仮定義が存在している場合はどうなるかコンパイラ依存です。VC++の場合は共有されます。
link : http://msdn.microsoft.com/ja-jp/library/k8w8btzz.aspx
// main.c
int x; // extern int x = 0;として扱われる
// sub.c
int x; // extern int x; として扱われる
なお、externは同じファイル内に既に宣言がある場合、それに従います。つまり、extern宣言(=なしのものです)は、定義はしないで宣言だけすることを明示します。externは仮定義にはさせません!
※ 2/28 ご指摘の通り、私の理解が間違っていたので修正させて頂きました。(打ち消し線のものは古い文章で、下線のものは追加した文章です。)
最後に編集したユーザー milfeulle on 2014年2月28日(金) 00:05 [ 編集 1 回目 ]
ζ*'ヮ')ζプログラミングはみんなで奏でるシンフォニー
Re: ヘッダーファイルでの変数について
仮定義というのは、コンパイル単位の最後まで仮定義のままだったら、0に等しい初期値を持った定義があるとみなしてコンパイル単位ごとにコンパイルするというもの(規格では同じ規則で動作するという記述)であって、さかのぼってextern付きの宣言とみなしたり定義に変わったりするものではありません。
staticが付いた宣言も初期値がないものは仮定義です。
内部結合と外部結合が同時に指定されることにはなりません。
externの付いた宣言は内部結合の外部変数を参照することもあります。
プログラム全体で仮定義しか無かった場合、定義がひとつ作成されます。
プログラム全体で定義はひとつだけ存在できます。
C++では、初期値のない外部変数の宣言はその時点で0に等しい初期値を持った定義となります。
namespace A { int a; }は、A::aという名前のオブジェクトの宣言であり、名前単位で同じ規則が適用されます。
staticが付いた宣言も初期値がないものは仮定義です。
内部結合と外部結合が同時に指定されることにはなりません。
externの付いた宣言は内部結合の外部変数を参照することもあります。
プログラム全体で仮定義しか無かった場合、定義がひとつ作成されます。
プログラム全体で定義はひとつだけ存在できます。
C++では、初期値のない外部変数の宣言はその時点で0に等しい初期値を持った定義となります。
namespace A { int a; }は、A::aという名前のオブジェクトの宣言であり、名前単位で同じ規則が適用されます。
Re: ヘッダーファイルでの変数について
質問の例の場合、
hoge.cにインクルードされた外部変数hogeの宣言は仮定義となります。
main.cにインクルードされた外部変数hogeの宣言は仮定義となります。
これらがリンクされるとき、仮定義しかないので定義がひとつ作成され、それを参照する実行ファイルが作成されます。
hoge.cppにインクルードされた外部変数hogeの宣言は定義となります。
main.cppにインクルードされた外部変数hogeの宣言は定義となります。
これらがリンクされるとき、定義が複数存在するのでリンクエラーとなります。
ちなみに仮定義は、プログラム全体で定義がひとつだけ存在するとき、例え0に等しい初期値でなくても、それを参照します。
ライブラリ等どこかのだれかが作ったオブジェクトとリンクする際、名前がかぶるだけで共有してしまいます。
#規格上、定義の不一致は未定義の動作
自分だけがコードを書くときに初期値を設定しても共有を防ぐことはできません。
C言語のグローバル変数は永遠にバグを生む可能性から逃れることのできない恐ろしいものなのです。
hoge.cにインクルードされた外部変数hogeの宣言は仮定義となります。
main.cにインクルードされた外部変数hogeの宣言は仮定義となります。
これらがリンクされるとき、仮定義しかないので定義がひとつ作成され、それを参照する実行ファイルが作成されます。
hoge.cppにインクルードされた外部変数hogeの宣言は定義となります。
main.cppにインクルードされた外部変数hogeの宣言は定義となります。
これらがリンクされるとき、定義が複数存在するのでリンクエラーとなります。
ちなみに仮定義は、プログラム全体で定義がひとつだけ存在するとき、例え0に等しい初期値でなくても、それを参照します。
ライブラリ等どこかのだれかが作ったオブジェクトとリンクする際、名前がかぶるだけで共有してしまいます。
#規格上、定義の不一致は未定義の動作
自分だけがコードを書くときに初期値を設定しても共有を防ぐことはできません。
C言語のグローバル変数は永遠にバグを生む可能性から逃れることのできない恐ろしいものなのです。