ヘッダーファイルでの変数について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
cloud

ヘッダーファイルでの変数について

#1

投稿記事 by cloud » 10年前

ふとexternの扱いについて勉強していたんですが、
もしヘッダーに宣言する場合は変数はexternを使うと思うんですが、
以下の様に書いてみると、

コード:

// hoge.h
#pragma once
int hoge;

コード:

// hoge.c
#include "hoge.h"

コード:

// main.c
#include "hoge.h"
コンパイルがとおり、hoge.cとmain.cでは変数hogeは同じでした。
しかし、CファイルをCPPファイルに変更すると、
リンクできなくなり、「複数回定義されている」とエラーがでてきました。
ヘッダーの方はexternをつけないといけないと思っていたのですが、
CPPファイルだとちゃんとエラーがでるのにCファイルだとリンクできて実行もできてしまう。
これは、なぜなんでしょう。Cファイルではexternが省略されているのでしょうか。
どなたかご教授おねがいします。
ちなみに環境は
・Windows7
・VisualStudio2010Professional
です。

アバター
milfeulle
記事: 47
登録日時: 10年前
住所: マリーランド
連絡を取る:

Re: ヘッダーファイルでの変数について

#2

投稿記事 by milfeulle » 10年前

C言語とC++で決定的に異なるのが、namespaceの存在と仮定義の有無です。externは関係ありません。

C言語にはnamespaceの概念がなく、仮定義があります。一方でC++にはnamespaceがあり、仮定義がありません。どういったことでしょう?

namespaceは分かりやすいですね。

コード:

namespace A {
なんちゃら
}
これは複数のファイルに同じ名前のnamespaceがあっても構わないわけですが、それらはC言語で言う1つの繋がったファイルのような名前空間となります(外部リンケージを持つ変数等に限ります*註)。そして、C++では、1つのnamespaceのなかに同じ宣言を2つ書いた場合、エラーとなります。
オフトピック
*註
同じ名前空間であっても、内部リンケージを持つような宣言、例えばstaticがついていたりconstが付いていたりした場合は、そのファイル内でのみ参照されます。従って別のファイルで定義してもそれぞれ別と見なすのでエラーになりません。

コード:

namespace A {
	int x;
	int x; // 重複定義でコンパイルエラー
}
ところで、namespaceを書かなかった場合の扱いですが、全てがグローバルのnamespaceとなります。
オフトピック
名前なしのnamespaceとは異なります。

コード:

namespace {
	int x; // これと
}
int x; // これは別物
グローバルなnamespaceの中で、同じ変数名で定義しようとするとエラーが起きます。これはC++で問題のプログラムがコンパイルできない原因だと考えられます。C++では(externなど付けなければ)書いたら定義されます。

一方で、C言語の場合は仮定義というものが存在しています。これは、externなどのストレージクラス指定子と初期化指定子(=で初期値を指定)がない定義です。

コード:

// main.c
int x; // 仮定義
仮定義は、1つのファイルの(翻訳単位の)コンパイルが終わるまでに実際の定義が現れなかった場合、実際の定義となります。この際は0で初期化されます。

コード:

// main.c
int x; // 仮定義
int x; // 仮定義
int x = 3; // 完全定義
2つの定義を一緒に書くことはできません。

コード:

// main.c
int x = 2; // 完全定義
int x; // 未定義
int x = 3; // 完全定義(重複)
このように初期化なしで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 回目 ]
ζ*'ヮ')ζプログラミングはみんなで奏でるシンフォニー

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: ヘッダーファイルでの変数について

#3

投稿記事 by ISLe » 10年前

仮定義というのは、コンパイル単位の最後まで仮定義のままだったら、0に等しい初期値を持った定義があるとみなしてコンパイル単位ごとにコンパイルするというもの(規格では同じ規則で動作するという記述)であって、さかのぼってextern付きの宣言とみなしたり定義に変わったりするものではありません。

staticが付いた宣言も初期値がないものは仮定義です。
内部結合と外部結合が同時に指定されることにはなりません。

externの付いた宣言は内部結合の外部変数を参照することもあります。

プログラム全体で仮定義しか無かった場合、定義がひとつ作成されます。
プログラム全体で定義はひとつだけ存在できます。

C++では、初期値のない外部変数の宣言はその時点で0に等しい初期値を持った定義となります。

namespace A { int a; }は、A::aという名前のオブジェクトの宣言であり、名前単位で同じ規則が適用されます。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: ヘッダーファイルでの変数について

#4

投稿記事 by ISLe » 10年前

質問の例の場合、

hoge.cにインクルードされた外部変数hogeの宣言は仮定義となります。
main.cにインクルードされた外部変数hogeの宣言は仮定義となります。
これらがリンクされるとき、仮定義しかないので定義がひとつ作成され、それを参照する実行ファイルが作成されます。

hoge.cppにインクルードされた外部変数hogeの宣言は定義となります。
main.cppにインクルードされた外部変数hogeの宣言は定義となります。
これらがリンクされるとき、定義が複数存在するのでリンクエラーとなります。


ちなみに仮定義は、プログラム全体で定義がひとつだけ存在するとき、例え0に等しい初期値でなくても、それを参照します。
ライブラリ等どこかのだれかが作ったオブジェクトとリンクする際、名前がかぶるだけで共有してしまいます。
#規格上、定義の不一致は未定義の動作
自分だけがコードを書くときに初期値を設定しても共有を防ぐことはできません。
C言語のグローバル変数は永遠にバグを生む可能性から逃れることのできない恐ろしいものなのです。

閉鎖

“C言語何でも質問掲示板” へ戻る